From 2d2fc74110f2fe7fbc06d7f6e0a2e90528ce46c6 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 Aug 2012 18:37:21 +0300 Subject: [PATCH] get rid of the legacy L.Transition, replace with a better and simpler L.PosAnimation --- build/deps.js | 29 +++--- debug/leaflet-include.js | 8 +- src/dom/DomUtil.js | 3 + src/dom/Draggable.js | 3 +- src/dom/PosAnimation.Timer.js | 64 ++++++++++++ src/dom/PosAnimation.js | 64 ++++++++++++ src/dom/transition/Transition.Native.js | 123 ------------------------ src/dom/transition/Transition.Timer.js | 105 -------------------- src/dom/transition/Transition.js | 28 ------ src/map/anim/Map.PanAnimation.js | 17 ++-- src/map/anim/Map.ZoomAnimation.js | 2 +- src/map/handler/Map.Drag.js | 21 ++-- 12 files changed, 166 insertions(+), 301 deletions(-) create mode 100644 src/dom/PosAnimation.Timer.js create mode 100644 src/dom/PosAnimation.js delete mode 100644 src/dom/transition/Transition.Native.js delete mode 100644 src/dom/transition/Transition.Timer.js delete mode 100644 src/dom/transition/Transition.js diff --git a/build/deps.js b/build/deps.js index 400bc437..2692b534 100644 --- a/build/deps.js +++ b/build/deps.js @@ -228,33 +228,28 @@ var deps = { }, - AnimationNative: { - src: ['dom/DomEvent.js', - 'dom/transition/Transition.js', - 'dom/transition/Transition.Native.js'], - desc: 'Animation core that uses CSS3 Transitions (for powering pan & zoom animations). Works on mobile webkit-powered browsers and some modern desktop browsers.', - heading: 'Visual effects' + AnimationPan: { + src: [ + 'dom/DomEvent.js', + 'dom/PosAnimation.js', + 'map/anim/Map.PanAnimation.js' + ], + deps: ['AnimationPan'], + desc: 'Core panning animation support.' }, AnimationTimer: { - src: ['dom/transition/Transition.Timer.js'], - deps: ['AnimationNative'], - desc: 'Timer-based animation fallback for browsers that don\'t support CSS3 transitions.' - }, - - AnimationPan: { - src: ['map/anim/Map.PanAnimation.js'], + src: ['dom/PosAnimation.Timer.js'], deps: ['AnimationPan'], - desc: 'Panning animation. Can use both native and timer-based animation.' + desc: 'Timer-based pan animation fallback for browsers that don\'t support CSS3 transitions.' }, AnimationZoom: { src: ['map/anim/Map.ZoomAnimation.js'], - deps: ['AnimationPan', 'AnimationNative'], - desc: 'Smooth zooming animation. So far it works only on browsers that support CSS3 Transitions.' + deps: ['AnimationPan'], + desc: 'Smooth zooming animation. Works only on browsers that support CSS3 Transitions.' }, - Geolocation: { src: ['map/ext/Map.Geolocation.js'], desc: 'Adds Map#locate method and related events to make geolocation easier.', diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js index d4444505..57125a9f 100644 --- a/debug/leaflet-include.js +++ b/debug/leaflet-include.js @@ -19,9 +19,11 @@ 'dom/DomUtil.js', 'dom/Draggable.js', - 'dom/transition/Transition.js', - 'dom/transition/Transition.Native.js', - 'dom/transition/Transition.Timer.js', + 'dom/PosAnimation.js', + 'dom/PosAnimation.Timer.js', + // 'dom/transition/Transition.js', + // 'dom/transition/Transition.Native.js', + // 'dom/transition/Transition.Timer.js', 'geo/LatLng.js', 'geo/LatLngBounds.js', diff --git a/src/dom/DomUtil.js b/src/dom/DomUtil.js index 65eab4c8..d161b9dd 100644 --- a/src/dom/DomUtil.js +++ b/src/dom/DomUtil.js @@ -184,3 +184,6 @@ L.Util.extend(L.DomUtil, { TRANSITION: L.DomUtil.testProp(['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']), TRANSFORM: L.DomUtil.testProp(['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']) }); + +L.DomUtil.TRANSITION_END = (L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? + L.DomUtil.TRANSITION + 'End' : 'transitionend'); diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index f2e3cca7..e3385f71 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -60,7 +60,6 @@ L.Draggable = L.Class.extend({ return; } - this._startPos = this._newPos = L.DomUtil.getPosition(this._element); this._startPoint = new L.Point(first.clientX, first.clientY); L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this); @@ -82,6 +81,8 @@ L.Draggable = L.Class.extend({ this.fire('dragstart'); this._moved = true; + this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec); + if (!L.Browser.touch) { L.DomUtil.disableTextSelection(); this._setMovingCursor(); diff --git a/src/dom/PosAnimation.Timer.js b/src/dom/PosAnimation.Timer.js new file mode 100644 index 00000000..e2edcc24 --- /dev/null +++ b/src/dom/PosAnimation.Timer.js @@ -0,0 +1,64 @@ +/* + * L.PosAnimation fallback implementation that powers Leaflet pan animations + * in browsers that don't support CSS3 Transitions + */ + +L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.Class.extend({ + includes: L.Mixin.Events, + + run: function (el, newPos, duration) { + this.stop(); + + this._el = el; + this._inProgress = true; + this._duration = duration; + + this._startPos = L.DomUtil.getPosition(el); + this._offset = newPos.subtract(this._startPos); + this._startTime = +new Date(); + + this.fire('start'); + + this._animate(); + }, + + stop: function () { + if (!this._inProgress) { return; } + + this._step(); + this._complete(); + }, + + _animate: function () { + this._animId = L.Util.requestAnimFrame(this._animate, this, false, this._el); + this._step(); + }, + + _step: function () { + var elapsed = (+new Date()) - this._startTime, + duration = this._duration * 1000; + + if (elapsed < duration) { + this._runFrame(this._easeOut(elapsed / duration)); + } else { + this._runFrame(1); + this._complete(); + } + }, + + _runFrame: function (progress) { + var pos = this._startPos.add(this._offset.multiplyBy(progress)); + L.DomUtil.setPosition(this._el, pos); + this.fire('step'); + }, + + _complete: function () { + L.Util.cancelAnimFrame(this._animId); + this.fire('end'); + this._inProgress = false; + }, + + _easeOut: function (t) { + return t * (2 - t); + } +}); diff --git a/src/dom/PosAnimation.js b/src/dom/PosAnimation.js new file mode 100644 index 00000000..de3c88e2 --- /dev/null +++ b/src/dom/PosAnimation.js @@ -0,0 +1,64 @@ +/* + * L.PosAnimation is used by Leaflet internally for pan animations + */ + +L.PosAnimation = L.Class.extend({ + includes: L.Mixin.Events, + + run: function (el, newPos, duration) { + this.stop(); + + this._el = el; + this._inProgress = true; + + this.fire('start'); + + el.style[L.DomUtil.TRANSITION] = 'all ' + duration + 's ease-out'; + + L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); + L.DomUtil.setPosition(el, newPos); + + // Chrome flickers for some reason if you don't do this + L.Util.falseFn(el.offsetWidth); + }, + + stop: function () { + if (!this._inProgress) { return; } + + var pos = this._getPos(); + + L.DomUtil.setPosition(this._el, pos); + this._onTransitionEnd(); + }, + + _transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/, + + _getPos: function () { + var left, top, matches, + el = this._el, + style = window.getComputedStyle(el); + + if (L.Browser.any3d) { + matches = style[L.DomUtil.TRANSFORM].match(this._transformRe); + left = parseFloat(matches[1]); + top = parseFloat(matches[2]); + } else { + left = parseFloat(style.left); + top = parseFloat(style.top); + } + + return new L.Point(left, top, true); + }, + + _onTransitionEnd: function () { + L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); + + if (!this._inProgress) { return; } + this._inProgress = false; + + this._el.style[L.DomUtil.TRANSITION] = ''; + + this.fire('end'); + } + +}); diff --git a/src/dom/transition/Transition.Native.js b/src/dom/transition/Transition.Native.js deleted file mode 100644 index a0e8e5df..00000000 --- a/src/dom/transition/Transition.Native.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - * L.Transition native implementation that powers Leaflet animation - * in browsers that support CSS3 Transitions - */ - -L.Transition = L.Transition.extend({ - statics: (function () { - var transition = L.DomUtil.TRANSITION, - transitionEnd = (transition === 'webkitTransition' || transition === 'OTransition' ? - transition + 'End' : 'transitionend'); - - return { - NATIVE: !!transition, - - TRANSITION: transition, - PROPERTY: transition + 'Property', - DURATION: transition + 'Duration', - EASING: transition + 'TimingFunction', - END: transitionEnd, - - // transition-property value to use with each particular custom property - CUSTOM_PROPS_PROPERTIES: { - position: L.Browser.any3d ? L.DomUtil.TRANSFORM : 'top, left' - } - }; - }()), - - options: { - fakeStepInterval: 100 - }, - - initialize: function (/*HTMLElement*/ el, /*Object*/ options) { - this._el = el; - L.Util.setOptions(this, options); - - L.DomEvent.on(el, L.Transition.END, this._onTransitionEnd, this); - this._onFakeStep = L.Util.bind(this._onFakeStep, this); - }, - - run: function (/*Object*/ props) { - var prop, - propsList = [], - customProp = L.Transition.CUSTOM_PROPS_PROPERTIES; - - for (prop in props) { - if (props.hasOwnProperty(prop)) { - prop = customProp[prop] ? customProp[prop] : prop; - prop = this._dasherize(prop); - propsList.push(prop); - } - } - - this._el.style[L.Transition.DURATION] = this.options.duration + 's'; - this._el.style[L.Transition.EASING] = this.options.easing; - this._el.style[L.Transition.PROPERTY] = 'all'; - - for (prop in props) { - if (props.hasOwnProperty(prop)) { - this._setProperty(prop, props[prop]); - } - } - - // Chrome flickers for some reason if you don't do this - L.Util.falseFn(this._el.offsetWidth); - - this._inProgress = true; - - if (L.Browser.mobileWebkit) { - // Set up a slightly delayed call to a backup event if webkitTransitionEnd doesn't fire properly - this.backupEventFire = setTimeout(L.Util.bind(this._onBackupFireEnd, this), this.options.duration * 1.2 * 1000); - } - - if (L.Transition.NATIVE) { - clearInterval(this._timer); - this._timer = setInterval(this._onFakeStep, this.options.fakeStepInterval); - } else { - this._onTransitionEnd(); - } - }, - - _dasherize: (function () { - var re = /([A-Z])/g; - - function replaceFn(w) { - return '-' + w.toLowerCase(); - } - - return function (str) { - return str.replace(re, replaceFn); - }; - }()), - - _onFakeStep: function () { - this.fire('step'); - }, - - _onTransitionEnd: function (e) { - if (this._inProgress) { - this._inProgress = false; - clearInterval(this._timer); - - this._el.style[L.Transition.TRANSITION] = ''; - - // Clear the delayed call to the backup event, we have recieved some form of webkitTransitionEnd - clearTimeout(this.backupEventFire); - delete this.backupEventFire; - - this.fire('step'); - - if (e && e.type) { - this.fire('end'); - } - } - }, - - _onBackupFireEnd: function () { - // Create and fire a transitionEnd event on the element. - - var event = document.createEvent("Event"); - event.initEvent(L.Transition.END, true, false); - this._el.dispatchEvent(event); - } -}); diff --git a/src/dom/transition/Transition.Timer.js b/src/dom/transition/Transition.Timer.js deleted file mode 100644 index 7303ee14..00000000 --- a/src/dom/transition/Transition.Timer.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * L.Transition fallback implementation that powers Leaflet animation - * in browsers that don't support CSS3 Transitions - */ - -L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({ - statics: { - getTime: Date.now || function () { - return +new Date(); - }, - - TIMER: true, - - EASINGS: { - 'linear': function (t) { return t; }, - 'ease-out': function (t) { return t * (2 - t); } - }, - - CUSTOM_PROPS_GETTERS: { - position: L.DomUtil.getPosition - }, - - //used to get units from strings like "10.5px" (->px) - UNIT_RE: /^[\d\.]+(\D*)$/ - }, - - options: { - fps: 50 - }, - - initialize: function (el, options) { - this._el = el; - L.Util.extend(this.options, options); - - this._easing = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS['ease-out']; - - this._step = L.Util.bind(this._step, this); - this._interval = Math.round(1000 / this.options.fps); - }, - - run: function (props) { - this._props = {}; - - var getters = L.Transition.CUSTOM_PROPS_GETTERS, - re = L.Transition.UNIT_RE; - - this.fire('start'); - - for (var prop in props) { - if (props.hasOwnProperty(prop)) { - var p = {}; - if (prop in getters) { - p.from = getters[prop](this._el); - } else { - var matches = this._el.style[prop].match(re); - p.from = parseFloat(matches[0]); - p.unit = matches[1]; - } - p.to = props[prop]; - this._props[prop] = p; - } - } - - clearInterval(this._timer); - this._timer = setInterval(this._step, this._interval); - this._startTime = L.Transition.getTime(); - }, - - _step: function () { - var time = L.Transition.getTime(), - elapsed = time - this._startTime, - duration = this.options.duration * 1000; - - if (elapsed < duration) { - this._runFrame(this._easing(elapsed / duration)); - } else { - this._runFrame(1); - this._complete(); - } - }, - - _runFrame: function (percentComplete) { - var setters = L.Transition.CUSTOM_PROPS_SETTERS, - prop, p, value; - - for (prop in this._props) { - if (this._props.hasOwnProperty(prop)) { - p = this._props[prop]; - if (prop in setters) { - value = p.to.subtract(p.from).multiplyBy(percentComplete).add(p.from); - setters[prop](this._el, value); - } else { - this._el.style[prop] = - ((p.to - p.from) * percentComplete + p.from) + p.unit; - } - } - } - this.fire('step'); - }, - - _complete: function () { - clearInterval(this._timer); - this.fire('end'); - } -}); diff --git a/src/dom/transition/Transition.js b/src/dom/transition/Transition.js deleted file mode 100644 index 4e9273b0..00000000 --- a/src/dom/transition/Transition.js +++ /dev/null @@ -1,28 +0,0 @@ -L.Transition = L.Class.extend({ - includes: L.Mixin.Events, - - statics: { - CUSTOM_PROPS_SETTERS: { - position: L.DomUtil.setPosition - //TODO transform custom attr - }, - - implemented: function () { - return L.Transition.NATIVE || L.Transition.TIMER; - } - }, - - options: { - easing: 'ease', - duration: 0.5 - }, - - _setProperty: function (prop, value) { - var setters = L.Transition.CUSTOM_PROPS_SETTERS; - if (prop in setters) { - setters[prop](this._el, value); - } else { - this._el.style[prop] = value; - } - } -}); diff --git a/src/map/anim/Map.PanAnimation.js b/src/map/anim/Map.PanAnimation.js index 37286532..0826f5ff 100644 --- a/src/map/anim/Map.PanAnimation.js +++ b/src/map/anim/Map.PanAnimation.js @@ -1,5 +1,5 @@ -L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : { +L.Map.include({ setView: function (center, zoom, forceReset) { zoom = this._limitZoom(zoom); @@ -24,31 +24,28 @@ L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : { return this; }, - panBy: function (offset, options) { + panBy: function (offset, duration) { offset = L.point(offset); if (!(offset.x || offset.y)) { return this; } - if (!this._panTransition) { - this._panTransition = new L.Transition(this._mapPane); + if (!this._panAnim) { + this._panAnim = new L.PosAnimation(); - this._panTransition.on({ + this._panAnim.on({ 'step': this._onPanTransitionStep, 'end': this._onPanTransitionEnd }, this); } - L.Util.setOptions(this._panTransition, L.Util.extend({duration: 0.25}, options)); - this.fire('movestart'); L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); - this._panTransition.run({ - position: L.DomUtil.getPosition(this._mapPane).subtract(offset) - }); + var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset); + this._panAnim.run(this._mapPane, newPos, duration || 0.25); return this; }, diff --git a/src/map/anim/Map.ZoomAnimation.js b/src/map/anim/Map.ZoomAnimation.js index 19a49fad..e36b7e6d 100644 --- a/src/map/anim/Map.ZoomAnimation.js +++ b/src/map/anim/Map.ZoomAnimation.js @@ -4,7 +4,7 @@ L.Map.mergeOptions({ if (L.DomUtil.TRANSITION) { L.Map.addInitHook(function () { - L.DomEvent.on(this._mapPane, L.Transition.END, this._catchTransitionEnd, this); + L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); }); } diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index 42852a8e..0c86f48e 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -6,9 +6,9 @@ L.Map.mergeOptions({ dragging: true, inertia: !L.Browser.android23, - inertiaDeceleration: 3000, // px/s^2 - inertiaMaxSpeed: 1500, // px/s - inertiaThreshold: L.Browser.touch ? 32 : 14, // ms + inertiaDeceleration: 5000, // px/s^2 + inertiaMaxSpeed: 4000, // px/s + inertiaThreshold: L.Browser.touch ? 32 : 18, // ms // TODO refactor, move to CRS worldCopyJump: true @@ -46,14 +46,14 @@ L.Map.Drag = L.Handler.extend({ _onDragStart: function () { var map = this._map; + if (map._panAnim) { + map._panAnim.stop(); + } + map .fire('movestart') .fire('dragstart'); - if (map._panTransition) { - map._panTransition._onTransitionEnd(true); - } - if (map.options.inertia) { this._positions = []; this._times = []; @@ -127,13 +127,8 @@ L.Map.Drag = L.Handler.extend({ decelerationDuration = limitedSpeed / options.inertiaDeceleration, offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); - var panOptions = { - duration: decelerationDuration, - easing: 'ease-out' - }; - L.Util.requestAnimFrame(L.Util.bind(function () { - this._map.panBy(offset, panOptions); + this._map.panBy(offset, decelerationDuration); }, this)); }