L.Map.include(!L.DomUtil.TRANSITION ? {} : {
	_zoomToIfCenterInView: function(center, zoom, centerOffset) {
		
		if (this._animatingZoom) { return true; }
		if (!this.options.zoomAnimation) { return false; }
		
		var zoomDelta = zoom - this._zoom,
			scale = Math.pow(2, zoomDelta),
			offset = centerOffset.divideBy(1 - 1/scale);
		
		//if offset does not exceed half of the view
		if (!this._offsetIsWithinView(offset, 1)) { return false; }
		
		this._mapPane.className += ' leaflet-zoom-anim';

		var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
			origin = centerPoint.add(offset);
		
		this._prepareTileBg();
	
		this._runAnimation(center, zoom, scale, origin);
		
		return true;
	},
	
	
	_runAnimation: function(center, zoom, scale, origin) {
		this._animatingZoom = true;

		this._animateToCenter = center;
		this._animateToZoom = zoom;
		
		var transform = L.DomUtil.TRANSFORM;
		
		//dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
		if (L.Browser.gecko || window.opera) {
			this._tileBg.style[transform] += ' translate(0,0)';
		}
		
		var scaleStr;
		
		// Android doesn't like translate/scale chains, transformOrigin + scale works better but 
		// it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack
		// TODO work around this prettier
		if (L.Browser.android) {
			this._tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
			scaleStr = 'scale(' + scale + ')';
		} else {
			scaleStr = L.DomUtil.getScaleString(scale, origin);
		}
		
		L.Util.falseFn(this._tileBg.offsetWidth); //hack to make sure transform is updated before running animation
		
		var options = {};
		options[transform] = this._tileBg.style[transform] + ' ' + scaleStr;
		this._tileBg.transition.run(options);
	},
	
	_prepareTileBg: function() {
		if (!this._tileBg) {
			this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
			this._tileBg.style.zIndex = 1;
		}

		var tilePane = this._tilePane,
			tileBg = this._tileBg;
		
		// prepare the background pane to become the main tile pane
		//tileBg.innerHTML = '';
		tileBg.style[L.DomUtil.TRANSFORM] = '';
		tileBg.style.visibility = 'hidden';
		
		// tells tile layers to reinitialize their containers
		tileBg.empty = true;
		tilePane.empty = false;

		this._tilePane = this._panes.tilePane = tileBg;
		this._tileBg = tilePane;
		
		if (!this._tileBg.transition) {
			this._tileBg.transition = new L.Transition(this._tileBg, {duration: 0.3, easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'});
			this._tileBg.transition.on('end', this._onZoomTransitionEnd, this);
		}
		
		this._stopLoadingBgTiles();
	},
	
	// stops loading all tiles in the background layer
	_stopLoadingBgTiles: function() {
		var tiles = [].slice.call(this._tileBg.getElementsByTagName('img'));
		
		for (var i = 0, len = tiles.length; i < len; i++) {
			if (!tiles[i].complete) {
				// tiles[i].src = '' - evil, doesn't cancel the request!
				tiles[i].parentNode.removeChild(tiles[i]);
				tiles[i] = null;
			}
		}
	},
	
	_onZoomTransitionEnd: function() {
		this._restoreTileFront();
		
		L.Util.falseFn(this._tileBg.offsetWidth);
		this._resetView(this._animateToCenter, this._animateToZoom, true);
		
		//TODO clear tileBg on map layersload
		
		this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
		this._animatingZoom = 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 = '';
		}
	}
});