Skip to content

Commit

Permalink
refactor TileLayer animation, fix #1140, #1437, #52, #1442
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mourner committed Feb 20, 2013
1 parent 0b14d71 commit 40a824f
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 151 deletions.
2 changes: 1 addition & 1 deletion build/deps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
},
Expand Down
117 changes: 117 additions & 0 deletions src/layer/tile/TileLayer.Anim.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
});
58 changes: 41 additions & 17 deletions src/layer/tile/TileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
}
},
Expand Down Expand Up @@ -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);
}
},

Expand Down
7 changes: 2 additions & 5 deletions src/map/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
},

Expand Down
Loading

0 comments on commit 40a824f

Please sign in to comment.