Canvas improvements (#5115)
* WIP refactor canvas clear * Fix clearing bounds, remov unnecessary code * Refactor redraw logic * Remove flicker on update * Fix code style * Add support for layer ordering with bringToFront/bringToBack * Fix redraw when layer moves * Add example for moving canvas layers * Fix code style * Use layer ordering for mouse events * Fix removing first or last layer
This commit is contained in:
parent
d28e3eeccb
commit
4c484462dc
51
debug/vector/moving-canvas.html
Normal file
51
debug/vector/moving-canvas.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Leaflet debug page</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../dist/leaflet.css" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../css/screen.css" />
|
||||||
|
|
||||||
|
<script type="text/javascript" src="../../build/deps.js"></script>
|
||||||
|
<script src="../leaflet-include.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var osmUrl = 'http://api.tiles.mapbox.com/v4/mapbox.light/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibGllZG1hbiIsImEiOiI3ZGFmOGI2ZWY0MTAyYzUyMjAxNjcxYjQzZjRkYzM3MSJ9.XoFXhnx2eXp0DJwL3aEzZg',
|
||||||
|
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
||||||
|
|
||||||
|
var map = L.map('map', {preferCanvas: true})
|
||||||
|
.setView([50.5, 30.51], 15)
|
||||||
|
.addLayer(osm);
|
||||||
|
|
||||||
|
var markers = [];
|
||||||
|
var colors = ['red', 'green', 'blue', 'purple', 'cyan', 'yellow'];
|
||||||
|
for (var i = 0; i < 20; i++) {
|
||||||
|
markers.push(L.circleMarker([50.5, 30.51], {color: colors[i % colors.length]}).addTo(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var t = new Date().getTime() / 1000;
|
||||||
|
markers.forEach(function(marker, i) {
|
||||||
|
var v = t * (1 + i / 10) + (12.5 * i) / 180 * Math.PI;
|
||||||
|
marker.setLatLng([
|
||||||
|
50.5 + (i % 2 ? 1 : -1) * Math.sin(v) * 0.005,
|
||||||
|
30.51 + (i % 3 ? 1 : -1) * Math.cos(v) * 0.005,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Util.requestAnimFrame(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -51,6 +51,16 @@ L.Canvas = L.Renderer.extend({
|
|||||||
this._ctx = container.getContext('2d');
|
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 () {
|
_update: function () {
|
||||||
if (this._map._animatingZoom && this._bounds) { return; }
|
if (this._map._animatingZoom && this._bounds) { return; }
|
||||||
|
|
||||||
@ -85,24 +95,53 @@ L.Canvas = L.Renderer.extend({
|
|||||||
_initPath: function (layer) {
|
_initPath: function (layer) {
|
||||||
this._updateDashArray(layer);
|
this._updateDashArray(layer);
|
||||||
this._layers[L.stamp(layer)] = 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) {
|
_addPath: function (layer) {
|
||||||
layer._removed = false;
|
this._requestRedraw(layer);
|
||||||
},
|
},
|
||||||
|
|
||||||
_removePath: function (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);
|
this._requestRedraw(layer);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updatePath: function (layer) {
|
_updatePath: function (layer) {
|
||||||
this._redrawBounds = layer._pxBounds;
|
// Redraw the union of the layer's old pixel
|
||||||
this._draw(true);
|
// bounds and the new pixel bounds.
|
||||||
|
this._extendRedrawBounds(layer);
|
||||||
layer._project();
|
layer._project();
|
||||||
layer._update();
|
layer._update();
|
||||||
this._draw();
|
// The redraw will extend the redraw bounds
|
||||||
this._redrawBounds = null;
|
// with the new pixel bounds.
|
||||||
|
this._requestRedraw(layer);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateStyle: function (layer) {
|
_updateStyle: function (layer) {
|
||||||
@ -125,47 +164,62 @@ L.Canvas = L.Renderer.extend({
|
|||||||
_requestRedraw: function (layer) {
|
_requestRedraw: function (layer) {
|
||||||
if (!this._map) { return; }
|
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;
|
var padding = (layer.options.weight || 0) + 1;
|
||||||
this._redrawBounds = this._redrawBounds || new L.Bounds();
|
this._redrawBounds = this._redrawBounds || new L.Bounds();
|
||||||
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
|
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
|
||||||
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
|
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
|
||||||
|
|
||||||
this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_redraw: function () {
|
_redraw: function () {
|
||||||
this._redrawRequest = null;
|
this._redrawRequest = null;
|
||||||
|
|
||||||
this._draw(true); // clear layers in redraw bounds
|
this._clear(); // clear layers in redraw bounds
|
||||||
this._draw(); // draw layers
|
this._draw(); // draw layers
|
||||||
|
|
||||||
this._redrawBounds = null;
|
this._redrawBounds = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_draw: function (clear) {
|
_clear: function () {
|
||||||
this._clear = clear;
|
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;
|
var layer, bounds = this._redrawBounds;
|
||||||
this._ctx.save();
|
this._ctx.save();
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
|
var size = bounds.getSize();
|
||||||
this._ctx.beginPath();
|
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();
|
this._ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var id in this._layers) {
|
this._drawing = true;
|
||||||
layer = this._layers[id];
|
|
||||||
|
for (var order = this._drawFirst; order; order = order.next) {
|
||||||
|
layer = order.layer;
|
||||||
if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
|
if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
|
||||||
layer._updatePath();
|
layer._updatePath();
|
||||||
}
|
}
|
||||||
if (clear && layer._removed) {
|
|
||||||
delete layer._removed;
|
|
||||||
delete this._layers[id];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._drawing = false;
|
||||||
|
|
||||||
this._ctx.restore(); // Restore state before clipping.
|
this._ctx.restore(); // Restore state before clipping.
|
||||||
},
|
},
|
||||||
|
|
||||||
_updatePoly: function (layer, closed) {
|
_updatePoly: function (layer, closed) {
|
||||||
|
if (!this._drawing) { return; }
|
||||||
|
|
||||||
var i, j, len2, p,
|
var i, j, len2, p,
|
||||||
parts = layer._parts,
|
parts = layer._parts,
|
||||||
@ -199,7 +253,7 @@ L.Canvas = L.Renderer.extend({
|
|||||||
|
|
||||||
_updateCircle: function (layer) {
|
_updateCircle: function (layer) {
|
||||||
|
|
||||||
if (layer._empty()) { return; }
|
if (!this._drawing || layer._empty()) { return; }
|
||||||
|
|
||||||
var p = layer._point,
|
var p = layer._point,
|
||||||
ctx = this._ctx,
|
ctx = this._ctx,
|
||||||
@ -224,23 +278,17 @@ L.Canvas = L.Renderer.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_fillStroke: function (ctx, layer) {
|
_fillStroke: function (ctx, layer) {
|
||||||
var clear = this._clear,
|
var options = layer.options;
|
||||||
options = layer.options;
|
|
||||||
|
|
||||||
ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
|
if (options.fill) {
|
||||||
|
ctx.globalAlpha = options.fillOpacity;
|
||||||
if (options.fill || clear) {
|
|
||||||
ctx.globalAlpha = clear ? 1 : options.fillOpacity;
|
|
||||||
ctx.fillStyle = options.fillColor || options.color;
|
ctx.fillStyle = options.fillColor || options.color;
|
||||||
ctx.fill(options.fillRule || 'evenodd');
|
ctx.fill(options.fillRule || 'evenodd');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.stroke && options.weight !== 0) {
|
if (options.stroke && options.weight !== 0) {
|
||||||
ctx.globalAlpha = clear ? 1 : options.opacity;
|
ctx.globalAlpha = options.opacity;
|
||||||
|
ctx.lineWidth = options.weight;
|
||||||
// if clearing shape, do it with the previously drawn line width
|
|
||||||
layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
|
|
||||||
|
|
||||||
ctx.strokeStyle = options.color;
|
ctx.strokeStyle = options.color;
|
||||||
ctx.lineCap = options.lineCap;
|
ctx.lineCap = options.lineCap;
|
||||||
ctx.lineJoin = options.lineJoin;
|
ctx.lineJoin = options.lineJoin;
|
||||||
@ -254,8 +302,8 @@ L.Canvas = L.Renderer.extend({
|
|||||||
_onClick: function (e) {
|
_onClick: function (e) {
|
||||||
var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
|
var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
|
||||||
|
|
||||||
for (var id in this._layers) {
|
for (var order = this._drawFirst; order; order = order.next) {
|
||||||
layer = this._layers[id];
|
layer = order.layer;
|
||||||
if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
|
if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
|
||||||
clickedLayer = layer;
|
clickedLayer = layer;
|
||||||
}
|
}
|
||||||
@ -285,10 +333,10 @@ L.Canvas = L.Renderer.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_handleMouseHover: function (e, point) {
|
_handleMouseHover: function (e, point) {
|
||||||
var id, layer, candidateHoveredLayer;
|
var layer, candidateHoveredLayer;
|
||||||
|
|
||||||
for (id in this._drawnLayers) {
|
for (var order = this._drawFirst; order; order = order.next) {
|
||||||
layer = this._drawnLayers[id];
|
layer = order.layer;
|
||||||
if (layer.options.interactive && layer._containsPoint(point)) {
|
if (layer.options.interactive && layer._containsPoint(point)) {
|
||||||
candidateHoveredLayer = layer;
|
candidateHoveredLayer = layer;
|
||||||
}
|
}
|
||||||
@ -313,10 +361,61 @@ L.Canvas = L.Renderer.extend({
|
|||||||
this._map._fireDOMEvent(e, type || e.type, layers);
|
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,
|
if (next) {
|
||||||
_bringToBack: L.Util.falseFn
|
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
|
// @namespace Browser; @property canvas: Boolean
|
||||||
|
Loading…
Reference in New Issue
Block a user