ractive
Version:
Next-generation DOM manipulation
194 lines (153 loc) • 3.86 kB
JavaScript
import isEqual from 'utils/isEqual';
import Promise from 'utils/Promise';
import normaliseKeypath from 'utils/normaliseKeypath';
import animations from 'shared/animations';
import Animation from 'Ractive/prototype/animate/Animation';
var noop = function () {}, noAnimation = {
stop: noop
};
export default function Ractive$animate ( keypath, to, options ) {
var promise,
fulfilPromise,
k,
animation,
animations,
easing,
duration,
step,
complete,
makeValueCollector,
currentValues,
collectValue,
dummy,
dummyOptions;
promise = new Promise( function ( fulfil ) { fulfilPromise = fulfil; });
// animate multiple keypaths
if ( typeof keypath === 'object' ) {
options = to || {};
easing = options.easing;
duration = options.duration;
animations = [];
// we don't want to pass the `step` and `complete` handlers, as they will
// run for each animation! So instead we'll store the handlers and create
// our own...
step = options.step;
complete = options.complete;
if ( step || complete ) {
currentValues = {};
options.step = null;
options.complete = null;
makeValueCollector = function ( keypath ) {
return function ( t, value ) {
currentValues[ keypath ] = value;
};
};
}
for ( k in keypath ) {
if ( keypath.hasOwnProperty( k ) ) {
if ( step || complete ) {
collectValue = makeValueCollector( k );
options = {
easing: easing,
duration: duration
};
if ( step ) {
options.step = collectValue;
}
}
options.complete = complete ? collectValue : noop;
animations.push( animate( this, k, keypath[k], options ) );
}
}
if ( step || complete ) {
dummyOptions = {
easing: easing,
duration: duration
};
if ( step ) {
dummyOptions.step = function ( t ) {
step( t, currentValues );
};
}
if ( complete ) {
promise.then( function ( t ) {
complete( t, currentValues );
});
}
dummyOptions.complete = fulfilPromise;
dummy = animate( this, null, null, dummyOptions );
animations.push( dummy );
}
return {
stop: function () {
var animation;
while ( animation = animations.pop() ) {
animation.stop();
}
if ( dummy ) {
dummy.stop();
}
}
};
}
// animate a single keypath
options = options || {};
if ( options.complete ) {
promise.then( options.complete );
}
options.complete = fulfilPromise;
animation = animate( this, keypath, to, options );
promise.stop = function () {
animation.stop();
};
return promise;
}
function animate ( root, keypath, to, options ) {
var easing, duration, animation, from;
if ( keypath ) {
keypath = normaliseKeypath( keypath );
}
if ( keypath !== null ) {
from = root.viewmodel.get( keypath );
}
// cancel any existing animation
// TODO what about upstream/downstream keypaths?
animations.abort( keypath, root );
// don't bother animating values that stay the same
if ( isEqual( from, to ) ) {
if ( options.complete ) {
options.complete( options.to );
}
return noAnimation;
}
// easing function
if ( options.easing ) {
if ( typeof options.easing === 'function' ) {
easing = options.easing;
}
else {
easing = root.easing[ options.easing ];
}
if ( typeof easing !== 'function' ) {
easing = null;
}
}
// duration
duration = ( options.duration === undefined ? 400 : options.duration );
// TODO store keys, use an internal set method
animation = new Animation({
keypath: keypath,
from: from,
to: to,
root: root,
duration: duration,
easing: easing,
interpolator: options.interpolator,
// TODO wrap callbacks if necessary, to use instance as context
step: options.step,
complete: options.complete
});
animations.add( animation );
root._animations.push( animation );
return animation;
}