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');
|
||||
},
|
||||
|
||||
_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
|
||||
|
Loading…
Reference in New Issue
Block a user