Refactored TileLayer animation so that it happens for each tile layer independently instead of animating the parent of all tile layers. Moved TileLayer animation code into a separate file (TileLayer.Anim.js). Fixes loads of bugs and makes the code easier to understand.
This commit is contained in:
parent
0b14d71d7a
commit
40a824fc97
@ -245,7 +245,7 @@ var deps = {
|
||||
},
|
||||
|
||||
AnimationZoom: {
|
||||
src: ['map/anim/Map.ZoomAnimation.js'],
|
||||
src: ['map/anim/Map.ZoomAnimation.js', 'layer/tile/TileLayer.Anim.js'],
|
||||
deps: ['AnimationPan'],
|
||||
desc: 'Smooth zooming animation. Works only on browsers that support CSS3 Transitions.'
|
||||
},
|
||||
|
117
src/layer/tile/TileLayer.Anim.js
Normal file
117
src/layer/tile/TileLayer.Anim.js
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
Zoom animation logic for L.TileLayer.
|
||||
*/
|
||||
|
||||
L.TileLayer.include({
|
||||
_animateZoom: function (e) {
|
||||
var firstFrame = false;
|
||||
|
||||
if (!this._animating) {
|
||||
this._animating = true;
|
||||
firstFrame = true;
|
||||
}
|
||||
|
||||
if (firstFrame) {
|
||||
this._prepareBgBuffer();
|
||||
}
|
||||
|
||||
var transform = L.DomUtil.TRANSFORM,
|
||||
bg = this._bgBuffer;
|
||||
|
||||
if (firstFrame) {
|
||||
//prevent bg buffer from clearing right after zoom
|
||||
clearTimeout(this._clearBgBufferTimer);
|
||||
|
||||
// hack to make sure transform is updated before running animation
|
||||
L.Util.falseFn(bg.offsetWidth);
|
||||
}
|
||||
|
||||
var scaleStr = L.DomUtil.getScaleString(e.scale, e.origin),
|
||||
oldTransform = bg.style[transform];
|
||||
|
||||
bg.style[transform] = e.backwards ?
|
||||
(e.delta ? L.DomUtil.getTranslateString(e.delta) : oldTransform) + ' ' + scaleStr :
|
||||
scaleStr + ' ' + oldTransform;
|
||||
},
|
||||
|
||||
_endZoomAnim: function () {
|
||||
var front = this._tileContainer,
|
||||
bg = this._bgBuffer;
|
||||
|
||||
front.style.visibility = '';
|
||||
front.style.zIndex = 2;
|
||||
|
||||
bg.style.zIndex = 1;
|
||||
|
||||
// force reflow
|
||||
L.Util.falseFn(bg.offsetWidth);
|
||||
|
||||
this._animating = false;
|
||||
},
|
||||
|
||||
_clearBgBuffer: function () {
|
||||
var map = this._map;
|
||||
|
||||
if (!map._animatingZoom && !map.touchZoom._zooming) {
|
||||
this._bgBuffer.innerHTML = '';
|
||||
this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
|
||||
}
|
||||
},
|
||||
|
||||
_prepareBgBuffer: function () {
|
||||
|
||||
var front = this._tileContainer,
|
||||
bg = this._bgBuffer;
|
||||
|
||||
// if foreground layer doesn't have many tiles but bg layer does,
|
||||
// keep the existing bg layer and just zoom it some more
|
||||
|
||||
if (bg && this._getLoadedTilesPercentage(bg) > 0.5 &&
|
||||
this._getLoadedTilesPercentage(front) < 0.5) {
|
||||
|
||||
front.style.visibility = 'hidden';
|
||||
this._stopLoadingImages(front);
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare the buffer to become the front tile pane
|
||||
bg.style.visibility = 'hidden';
|
||||
bg.style[L.DomUtil.TRANSFORM] = '';
|
||||
|
||||
// switch out the current layer to be the new bg layer (and vice-versa)
|
||||
this._tileContainer = bg;
|
||||
bg = this._bgBuffer = front;
|
||||
|
||||
this._stopLoadingImages(bg);
|
||||
},
|
||||
|
||||
_getLoadedTilesPercentage: function (container) {
|
||||
var tiles = container.getElementsByTagName('img'),
|
||||
i, len, count = 0;
|
||||
|
||||
for (i = 0, len = tiles.length; i < len; i++) {
|
||||
if (tiles[i].complete) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count / len;
|
||||
},
|
||||
|
||||
// stops loading all tiles in the background layer
|
||||
_stopLoadingImages: function (container) {
|
||||
var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
|
||||
i, len, tile;
|
||||
|
||||
for (i = 0, len = tiles.length; i < len; i++) {
|
||||
tile = tiles[i];
|
||||
|
||||
if (!tile.complete) {
|
||||
tile.onload = L.Util.falseFn;
|
||||
tile.onerror = L.Util.falseFn;
|
||||
tile.src = L.Util.emptyImageUrl;
|
||||
|
||||
tile.parentNode.removeChild(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -53,6 +53,7 @@ L.TileLayer = L.Class.extend({
|
||||
|
||||
onAdd: function (map) {
|
||||
this._map = map;
|
||||
this._animated = map.options.zoomAnimation && L.Browser.any3d;
|
||||
|
||||
// create a container div for tiles
|
||||
this._initContainer();
|
||||
@ -62,10 +63,17 @@ L.TileLayer = L.Class.extend({
|
||||
|
||||
// set up events
|
||||
map.on({
|
||||
'viewreset': this._resetCallback,
|
||||
'viewreset': this._reset,
|
||||
'moveend': this._update
|
||||
}, this);
|
||||
|
||||
if (this._animated) {
|
||||
map.on({
|
||||
'zoomanim': this._animateZoom,
|
||||
'zoomend': this._endZoomAnim
|
||||
}, this);
|
||||
}
|
||||
|
||||
if (!this.options.updateWhenIdle) {
|
||||
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
|
||||
map.on('move', this._limitedUpdate, this);
|
||||
@ -84,10 +92,17 @@ L.TileLayer = L.Class.extend({
|
||||
this._container.parentNode.removeChild(this._container);
|
||||
|
||||
map.off({
|
||||
'viewreset': this._resetCallback,
|
||||
'viewreset': this._reset,
|
||||
'moveend': this._update
|
||||
}, this);
|
||||
|
||||
if (this._animated) {
|
||||
map.off({
|
||||
'zoomanim': this._animateZoom,
|
||||
'zoomend': this._endZoomAnim
|
||||
}, this);
|
||||
}
|
||||
|
||||
if (!this.options.updateWhenIdle) {
|
||||
map.off('move', this._limitedUpdate, this);
|
||||
}
|
||||
@ -151,8 +166,7 @@ L.TileLayer = L.Class.extend({
|
||||
|
||||
redraw: function () {
|
||||
if (this._map) {
|
||||
this._map._panes.tilePane.empty = false;
|
||||
this._reset(true);
|
||||
this._reset({hard: true});
|
||||
this._update();
|
||||
}
|
||||
return this;
|
||||
@ -212,11 +226,20 @@ L.TileLayer = L.Class.extend({
|
||||
_initContainer: function () {
|
||||
var tilePane = this._map._panes.tilePane;
|
||||
|
||||
if (!this._container || tilePane.empty) {
|
||||
if (!this._container) {
|
||||
this._container = L.DomUtil.create('div', 'leaflet-layer');
|
||||
|
||||
this._updateZIndex();
|
||||
|
||||
if (this._animated) {
|
||||
var className = 'leaflet-tile-container leaflet-zoom-animated';
|
||||
|
||||
this._bgBuffer = L.DomUtil.create('div', className, this._container);
|
||||
this._tileContainer = L.DomUtil.create('div', className, this._container);
|
||||
} else {
|
||||
this._tileContainer = this._container;
|
||||
}
|
||||
|
||||
tilePane.appendChild(this._container);
|
||||
|
||||
if (this.options.opacity < 1) {
|
||||
@ -225,11 +248,7 @@ L.TileLayer = L.Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_resetCallback: function (e) {
|
||||
this._reset(e.hard);
|
||||
},
|
||||
|
||||
_reset: function (clearOldContainer) {
|
||||
_reset: function (e) {
|
||||
var tiles = this._tiles;
|
||||
|
||||
for (var key in tiles) {
|
||||
@ -245,8 +264,10 @@ L.TileLayer = L.Class.extend({
|
||||
this._unusedTiles = [];
|
||||
}
|
||||
|
||||
if (clearOldContainer && this._container) {
|
||||
this._container.innerHTML = "";
|
||||
this._tileContainer.innerHTML = "";
|
||||
|
||||
if (this._animated && e && e.hard) {
|
||||
this._clearBgBuffer();
|
||||
}
|
||||
|
||||
this._initContainer();
|
||||
@ -319,7 +340,7 @@ L.TileLayer = L.Class.extend({
|
||||
this._addTile(queue[i], fragment);
|
||||
}
|
||||
|
||||
this._container.appendChild(fragment);
|
||||
this._tileContainer.appendChild(fragment);
|
||||
},
|
||||
|
||||
_tileShouldBeLoaded: function (tilePoint) {
|
||||
@ -365,8 +386,8 @@ L.TileLayer = L.Class.extend({
|
||||
L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
|
||||
this._unusedTiles.push(tile);
|
||||
|
||||
} else if (tile.parentNode === this._container) {
|
||||
this._container.removeChild(tile);
|
||||
} else if (tile.parentNode === this._tileContainer) {
|
||||
this._tileContainer.removeChild(tile);
|
||||
}
|
||||
|
||||
// for https://github.com/CloudMade/Leaflet/issues/137
|
||||
@ -386,7 +407,6 @@ L.TileLayer = L.Class.extend({
|
||||
/*
|
||||
Chrome 20 layouts much faster with top/left (verify with timeline, frames)
|
||||
Android 4 browser has display issues with top/left and requires transform instead
|
||||
Android 3 browser not tested
|
||||
Android 2 browser requires top/left or tiles disappear on load or first drag
|
||||
(reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
|
||||
(other browsers don't currently care) - see debug/hacks/jitter.html for an example
|
||||
@ -397,7 +417,7 @@ L.TileLayer = L.Class.extend({
|
||||
|
||||
this._loadTile(tile, tilePoint);
|
||||
|
||||
if (tile.parentNode !== this._container) {
|
||||
if (tile.parentNode !== this._tileContainer) {
|
||||
container.appendChild(tile);
|
||||
}
|
||||
},
|
||||
@ -499,6 +519,10 @@ L.TileLayer = L.Class.extend({
|
||||
this._tilesToLoad--;
|
||||
if (!this._tilesToLoad) {
|
||||
this.fire('load');
|
||||
|
||||
// clear scaled tiles after all new tiles are loaded (for performance)
|
||||
clearTimeout(this._clearBgBufferTimer);
|
||||
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -628,12 +628,9 @@ L.Map = L.Class.extend({
|
||||
},
|
||||
|
||||
_onTileLayerLoad: function () {
|
||||
// TODO super-ugly, refactor!!!
|
||||
// clear scaled tiles after all new tiles are loaded (for performance)
|
||||
this._tileLayersToLoad--;
|
||||
if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
|
||||
clearTimeout(this._clearTileBgTimer);
|
||||
this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
|
||||
if (this._tileLayersNum && !this._tileLayersToLoad) {
|
||||
this.fire('tilelayersload');
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -26,32 +26,21 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
|
||||
// if offset does not exceed half of the view
|
||||
if (!this._offsetIsWithinView(offset, 1)) { return false; }
|
||||
|
||||
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
|
||||
|
||||
this
|
||||
.fire('movestart')
|
||||
.fire('zoomstart');
|
||||
|
||||
this.fire('zoomanim', {
|
||||
center: center,
|
||||
zoom: zoom
|
||||
});
|
||||
|
||||
var origin = this._getCenterLayerPoint().add(offset);
|
||||
|
||||
this._prepareTileBg();
|
||||
this._runAnimation(center, zoom, scale, origin);
|
||||
this._animateZoom(center, zoom, origin, scale);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_catchTransitionEnd: function () {
|
||||
if (this._animatingZoom) {
|
||||
this._onZoomTransitionEnd();
|
||||
}
|
||||
},
|
||||
_animateZoom: function (center, zoom, origin, scale, delta, backwards) {
|
||||
|
||||
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
|
||||
|
||||
_runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
|
||||
this._animateToCenter = center;
|
||||
this._animateToZoom = zoom;
|
||||
this._animatingZoom = true;
|
||||
@ -60,110 +49,31 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
|
||||
L.Draggable._disabled = true;
|
||||
}
|
||||
|
||||
var transform = L.DomUtil.TRANSFORM,
|
||||
tileBg = this._tileBg;
|
||||
|
||||
clearTimeout(this._clearTileBgTimer);
|
||||
|
||||
L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
|
||||
|
||||
var scaleStr = L.DomUtil.getScaleString(scale, origin),
|
||||
oldTransform = tileBg.style[transform];
|
||||
|
||||
tileBg.style[transform] = backwardsTransform ?
|
||||
oldTransform + ' ' + scaleStr :
|
||||
scaleStr + ' ' + oldTransform;
|
||||
this.fire('zoomanim', {
|
||||
center: center,
|
||||
zoom: zoom,
|
||||
origin: origin,
|
||||
scale: scale,
|
||||
delta: delta,
|
||||
backwards: backwards
|
||||
});
|
||||
},
|
||||
|
||||
_prepareTileBg: function () {
|
||||
var tilePane = this._tilePane,
|
||||
tileBg = this._tileBg;
|
||||
|
||||
// If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
|
||||
if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
|
||||
this._getLoadedTilesPercentage(tilePane) < 0.5) {
|
||||
|
||||
tilePane.style.visibility = 'hidden';
|
||||
tilePane.empty = true;
|
||||
this._stopLoadingImages(tilePane);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tileBg) {
|
||||
tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
|
||||
tileBg.style.zIndex = 1;
|
||||
}
|
||||
|
||||
// prepare the background pane to become the main tile pane
|
||||
tileBg.style[L.DomUtil.TRANSFORM] = '';
|
||||
tileBg.style.visibility = 'hidden';
|
||||
|
||||
// tells tile layers to reinitialize their containers
|
||||
tileBg.empty = true; //new FG
|
||||
tilePane.empty = false; //new BG
|
||||
|
||||
//Switch out the current layer to be the new bg layer (And vice-versa)
|
||||
this._tilePane = this._panes.tilePane = tileBg;
|
||||
var newTileBg = this._tileBg = tilePane;
|
||||
|
||||
L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
|
||||
|
||||
this._stopLoadingImages(newTileBg);
|
||||
},
|
||||
|
||||
_getLoadedTilesPercentage: function (container) {
|
||||
var tiles = container.getElementsByTagName('img'),
|
||||
i, len, count = 0;
|
||||
|
||||
for (i = 0, len = tiles.length; i < len; i++) {
|
||||
if (tiles[i].complete) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count / len;
|
||||
},
|
||||
|
||||
// stops loading all tiles in the background layer
|
||||
_stopLoadingImages: function (container) {
|
||||
var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
|
||||
i, len, tile;
|
||||
|
||||
for (i = 0, len = tiles.length; i < len; i++) {
|
||||
tile = tiles[i];
|
||||
|
||||
if (!tile.complete) {
|
||||
tile.onload = L.Util.falseFn;
|
||||
tile.onerror = L.Util.falseFn;
|
||||
tile.src = L.Util.emptyImageUrl;
|
||||
|
||||
tile.parentNode.removeChild(tile);
|
||||
}
|
||||
_catchTransitionEnd: function () {
|
||||
if (this._animatingZoom) {
|
||||
this._onZoomTransitionEnd();
|
||||
}
|
||||
},
|
||||
|
||||
_onZoomTransitionEnd: function () {
|
||||
this._restoreTileFront();
|
||||
|
||||
L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
|
||||
L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
|
||||
|
||||
this._animatingZoom = false;
|
||||
this._resetView(this._animateToCenter, this._animateToZoom, true, true);
|
||||
|
||||
if (L.Draggable) {
|
||||
L.Draggable._disabled = false;
|
||||
}
|
||||
},
|
||||
|
||||
_restoreTileFront: function () {
|
||||
this._tilePane.innerHTML = '';
|
||||
this._tilePane.style.visibility = '';
|
||||
this._tilePane.style.zIndex = 2;
|
||||
this._tileBg.style.zIndex = 1;
|
||||
},
|
||||
|
||||
_clearTileBg: function () {
|
||||
if (!this._animatingZoom && !this.touchZoom._zooming) {
|
||||
this._tileBg.innerHTML = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -57,12 +57,11 @@ L.Map.TouchZoom = L.Handler.extend({
|
||||
if (this._scale === 1) { return; }
|
||||
|
||||
if (!this._moved) {
|
||||
L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
|
||||
L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
|
||||
|
||||
map
|
||||
.fire('movestart')
|
||||
.fire('zoomstart')
|
||||
._prepareTileBg();
|
||||
.fire('zoomstart');
|
||||
|
||||
this._moved = true;
|
||||
}
|
||||
@ -77,19 +76,10 @@ L.Map.TouchZoom = L.Handler.extend({
|
||||
_updateOnMove: function () {
|
||||
var map = this._map,
|
||||
origin = this._getScaleOrigin(),
|
||||
center = map.layerPointToLatLng(origin);
|
||||
center = map.layerPointToLatLng(origin),
|
||||
zoom = map.getScaleZoom(this._scale);
|
||||
|
||||
map.fire('zoomanim', {
|
||||
center: center,
|
||||
zoom: map.getScaleZoom(this._scale)
|
||||
});
|
||||
|
||||
// Used 2 translates instead of transform-origin because of a very strange bug -
|
||||
// it didn't count the origin on the first touch-zoom but worked correctly afterwards
|
||||
|
||||
map._tileBg.style[L.DomUtil.TRANSFORM] =
|
||||
L.DomUtil.getTranslateString(this._delta) + ' ' +
|
||||
L.DomUtil.getScaleString(this._scale, this._startCenter);
|
||||
map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, true);
|
||||
},
|
||||
|
||||
_onTouchEnd: function () {
|
||||
@ -112,14 +102,10 @@ L.Map.TouchZoom = L.Handler.extend({
|
||||
roundZoomDelta = (floatZoomDelta > 0 ?
|
||||
Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
|
||||
|
||||
zoom = map._limitZoom(oldZoom + roundZoomDelta);
|
||||
zoom = map._limitZoom(oldZoom + roundZoomDelta),
|
||||
scale = map.getZoomScale(zoom) / this._scale;
|
||||
|
||||
map.fire('zoomanim', {
|
||||
center: center,
|
||||
zoom: zoom
|
||||
});
|
||||
|
||||
map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
|
||||
map._animateZoom(center, zoom, origin, scale, null, true);
|
||||
},
|
||||
|
||||
_getScaleOrigin: function () {
|
||||
|
Loading…
Reference in New Issue
Block a user