diff --git a/debug/vector/moving-canvas.html b/debug/vector/moving-canvas.html new file mode 100644 index 00000000..6189065a --- /dev/null +++ b/debug/vector/moving-canvas.html @@ -0,0 +1,51 @@ + + + + Leaflet debug page + + + + + + + + + + + + +
+ + + + diff --git a/src/layer/vector/Canvas.js b/src/layer/vector/Canvas.js index b777b569..a04a8dfd 100644 --- a/src/layer/vector/Canvas.js +++ b/src/layer/vector/Canvas.js @@ -51,6 +51,16 @@ L.Canvas = L.Renderer.extend({ this._ctx = container.getContext('2d'); }, + _updatePaths: function () { + var layer; + this._redrawBounds = null; + for (var id in this._layers) { + layer = this._layers[id]; + layer._update(); + } + this._redraw(); + }, + _update: function () { if (this._map._animatingZoom && this._bounds) { return; } @@ -85,24 +95,53 @@ L.Canvas = L.Renderer.extend({ _initPath: function (layer) { this._updateDashArray(layer); this._layers[L.stamp(layer)] = layer; + + var order = layer._order = { + layer: layer, + prev: this._drawLast, + next: null + }; + if (this._drawLast) { this._drawLast.next = order; } + this._drawLast = order; + this._drawFirst = this._drawFirst || this._drawLast; }, _addPath: function (layer) { - layer._removed = false; + this._requestRedraw(layer); }, _removePath: function (layer) { - layer._removed = true; + var order = layer._order; + var next = order.next; + var prev = order.prev; + + if (next) { + next.prev = prev; + } else { + this._drawLast = prev; + } + if (prev) { + prev.next = next; + } else { + this._drawFirst = next; + } + + delete layer._order; + + delete this._layers[L.stamp(layer)]; + this._requestRedraw(layer); }, _updatePath: function (layer) { - this._redrawBounds = layer._pxBounds; - this._draw(true); + // Redraw the union of the layer's old pixel + // bounds and the new pixel bounds. + this._extendRedrawBounds(layer); layer._project(); layer._update(); - this._draw(); - this._redrawBounds = null; + // The redraw will extend the redraw bounds + // with the new pixel bounds. + this._requestRedraw(layer); }, _updateStyle: function (layer) { @@ -125,47 +164,62 @@ L.Canvas = L.Renderer.extend({ _requestRedraw: function (layer) { if (!this._map) { return; } + this._extendRedrawBounds(layer); + this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); + }, + + _extendRedrawBounds: function (layer) { var padding = (layer.options.weight || 0) + 1; this._redrawBounds = this._redrawBounds || new L.Bounds(); this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); - - this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); }, _redraw: function () { this._redrawRequest = null; - this._draw(true); // clear layers in redraw bounds + this._clear(); // clear layers in redraw bounds this._draw(); // draw layers this._redrawBounds = null; }, - _draw: function (clear) { - this._clear = clear; + _clear: function () { + var bounds = this._redrawBounds; + if (bounds) { + var size = bounds.getSize(); + this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); + } else { + this._ctx.clearRect(0, 0, this._container.width, this._container.height); + } + }, + + _draw: function () { var layer, bounds = this._redrawBounds; this._ctx.save(); if (bounds) { + var size = bounds.getSize(); this._ctx.beginPath(); - this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y); + this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); this._ctx.clip(); } - for (var id in this._layers) { - layer = this._layers[id]; + this._drawing = true; + + for (var order = this._drawFirst; order; order = order.next) { + layer = order.layer; if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { layer._updatePath(); } - if (clear && layer._removed) { - delete layer._removed; - delete this._layers[id]; - } } + + this._drawing = false; + this._ctx.restore(); // Restore state before clipping. }, _updatePoly: function (layer, closed) { + if (!this._drawing) { return; } var i, j, len2, p, parts = layer._parts, @@ -199,7 +253,7 @@ L.Canvas = L.Renderer.extend({ _updateCircle: function (layer) { - if (layer._empty()) { return; } + if (!this._drawing || layer._empty()) { return; } var p = layer._point, ctx = this._ctx, @@ -224,23 +278,17 @@ L.Canvas = L.Renderer.extend({ }, _fillStroke: function (ctx, layer) { - var clear = this._clear, - options = layer.options; + var options = layer.options; - ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over'; - - if (options.fill || clear) { - ctx.globalAlpha = clear ? 1 : options.fillOpacity; + if (options.fill) { + ctx.globalAlpha = options.fillOpacity; ctx.fillStyle = options.fillColor || options.color; ctx.fill(options.fillRule || 'evenodd'); } if (options.stroke && options.weight !== 0) { - ctx.globalAlpha = clear ? 1 : options.opacity; - - // if clearing shape, do it with the previously drawn line width - layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight; - + ctx.globalAlpha = options.opacity; + ctx.lineWidth = options.weight; ctx.strokeStyle = options.color; ctx.lineCap = options.lineCap; ctx.lineJoin = options.lineJoin; @@ -254,8 +302,8 @@ L.Canvas = L.Renderer.extend({ _onClick: function (e) { var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; - for (var id in this._layers) { - layer = this._layers[id]; + for (var order = this._drawFirst; order; order = order.next) { + layer = order.layer; if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { clickedLayer = layer; } @@ -285,10 +333,10 @@ L.Canvas = L.Renderer.extend({ }, _handleMouseHover: function (e, point) { - var id, layer, candidateHoveredLayer; + var layer, candidateHoveredLayer; - for (id in this._drawnLayers) { - layer = this._drawnLayers[id]; + for (var order = this._drawFirst; order; order = order.next) { + layer = order.layer; if (layer.options.interactive && layer._containsPoint(point)) { candidateHoveredLayer = layer; } @@ -313,10 +361,61 @@ L.Canvas = L.Renderer.extend({ this._map._fireDOMEvent(e, type || e.type, layers); }, - // TODO _bringToFront & _bringToBack, pretty tricky + _bringToFront: function (layer) { + var order = layer._order; + var next = order.next; + var prev = order.prev; - _bringToFront: L.Util.falseFn, - _bringToBack: L.Util.falseFn + if (next) { + next.prev = prev; + } else { + // Already last + return; + } + if (prev) { + prev.next = next; + } else if (next) { + // Update first entry unless this is the + // signle entry + this._drawFirst = next; + } + + order.prev = this._drawLast; + this._drawLast.next = order; + + order.next = null; + this._drawLast = order; + + this._requestRedraw(layer); + }, + + _bringToBack: function (layer) { + var order = layer._order; + var next = order.next; + var prev = order.prev; + + if (prev) { + prev.next = next; + } else { + // Already first + return; + } + if (next) { + next.prev = prev; + } else if (prev) { + // Update last entry unless this is the + // signle entry + this._drawLast = prev; + } + + order.prev = null; + + order.next = this._drawFirst; + this._drawFirst.prev = order; + this._drawFirst = order; + + this._requestRedraw(layer); + } }); // @namespace Browser; @property canvas: Boolean