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:
Per Liedman 2016-11-18 15:22:35 +01:00 committed by Iván Sánchez Ortega
parent d28e3eeccb
commit 4c484462dc
2 changed files with 188 additions and 38 deletions

View 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 = '&copy; <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>

View File

@ -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