Permalink
Browse files

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
  • Loading branch information...
1 parent d28e3ee commit 4c484462dc4d646cb43adc0585401d28d878330d @perliedman perliedman committed with IvanSanchez Nov 18, 2016
Showing with 188 additions and 38 deletions.
  1. +51 −0 debug/vector/moving-canvas.html
  2. +137 −38 src/layer/vector/Canvas.js
@@ -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>
@@ -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;
-
- ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
+ var options = layer.options;
- 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;
+
+ 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;
- _bringToFront: L.Util.falseFn,
- _bringToBack: L.Util.falseFn
+ 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

0 comments on commit 4c48446

Please sign in to comment.