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