get rid of the legacy L.Transition, replace with a better and simpler L.PosAnimation

This commit is contained in:
Vladimir Agafonkin 2012-08-10 18:37:21 +03:00
parent 8c381e3b50
commit 2d2fc74110
12 changed files with 166 additions and 301 deletions

View File

@ -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.',

View File

@ -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',

View File

@ -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');

View File

@ -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();

View File

@ -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);
}
});

64
src/dom/PosAnimation.js Normal file
View File

@ -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');
}
});

View File

@ -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);
}
});

View File

@ -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');
}
});

View File

@ -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;
}
}
});

View File

@ -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;
},

View File

@ -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);
});
}

View File

@ -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));
}