blob: 19201e628cf02ba1dd6d2edc619a8fbfb20f8097 [file] [log] [blame]
/**
* Animation main class, dispatch and manage all animation controllers
*
* @module zrender/animation/Animation
* @author pissang(https://github.com/pissang)
*/
// TODO Additive animation
// http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/
// https://developer.apple.com/videos/wwdc2014/#236
import * as util from '../core/util';
import { Dispatcher } from '../core/event';
import requestAnimationFrame from './requestAnimationFrame';
import Animator from './Animator';
/**
* @typedef {Object} IZRenderStage
* @property {Function} update
*/
/**
* @alias module:zrender/animation/Animation
* @constructor
* @param {Object} [options]
* @param {Function} [options.onframe]
* @param {IZRenderStage} [options.stage]
* @example
* var animation = new Animation();
* var obj = {
* x: 100,
* y: 100
* };
* animation.animate(node.position)
* .when(1000, {
* x: 500,
* y: 500
* })
* .when(2000, {
* x: 100,
* y: 100
* })
* .start('spline');
*/
var Animation = function (options) {
options = options || {};
this.stage = options.stage || {};
this.onframe = options.onframe || function () {}; // private properties
this._clips = [];
this._running = false;
this._time;
this._pausedTime;
this._pauseStart;
this._paused = false;
Dispatcher.call(this);
};
Animation.prototype = {
constructor: Animation,
/**
* Add clip
* @param {module:zrender/animation/Clip} clip
*/
addClip: function (clip) {
this._clips.push(clip);
},
/**
* Add animator
* @param {module:zrender/animation/Animator} animator
*/
addAnimator: function (animator) {
animator.animation = this;
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.addClip(clips[i]);
}
},
/**
* Delete animation clip
* @param {module:zrender/animation/Clip} clip
*/
removeClip: function (clip) {
var idx = util.indexOf(this._clips, clip);
if (idx >= 0) {
this._clips.splice(idx, 1);
}
},
/**
* Delete animation clip
* @param {module:zrender/animation/Animator} animator
*/
removeAnimator: function (animator) {
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.removeClip(clips[i]);
}
animator.animation = null;
},
_update: function () {
var time = new Date().getTime() - this._pausedTime;
var delta = time - this._time;
var clips = this._clips;
var len = clips.length;
var deferredEvents = [];
var deferredClips = [];
for (var i = 0; i < len; i++) {
var clip = clips[i];
var e = clip.step(time, delta); // Throw out the events need to be called after
// stage.update, like destroy
if (e) {
deferredEvents.push(e);
deferredClips.push(clip);
}
} // Remove the finished clip
for (var i = 0; i < len;) {
if (clips[i]._needsRemove) {
clips[i] = clips[len - 1];
clips.pop();
len--;
} else {
i++;
}
}
len = deferredEvents.length;
for (var i = 0; i < len; i++) {
deferredClips[i].fire(deferredEvents[i]);
}
this._time = time;
this.onframe(delta); // 'frame' should be triggered before stage, because upper application
// depends on the sequence (e.g., echarts-stream and finish
// event judge)
this.trigger('frame', delta);
if (this.stage.update) {
this.stage.update();
}
},
_startLoop: function () {
var self = this;
this._running = true;
function step() {
if (self._running) {
requestAnimationFrame(step);
!self._paused && self._update();
}
}
requestAnimationFrame(step);
},
/**
* Start animation.
*/
start: function () {
this._time = new Date().getTime();
this._pausedTime = 0;
this._startLoop();
},
/**
* Stop animation.
*/
stop: function () {
this._running = false;
},
/**
* Pause animation.
*/
pause: function () {
if (!this._paused) {
this._pauseStart = new Date().getTime();
this._paused = true;
}
},
/**
* Resume animation.
*/
resume: function () {
if (this._paused) {
this._pausedTime += new Date().getTime() - this._pauseStart;
this._paused = false;
}
},
/**
* Clear animation.
*/
clear: function () {
this._clips = [];
},
/**
* Whether animation finished.
*/
isFinished: function () {
return !this._clips.length;
},
/**
* Creat animator for a target, whose props can be animated.
*
* @param {Object} target
* @param {Object} options
* @param {boolean} [options.loop=false] Whether loop animation.
* @param {Function} [options.getter=null] Get value from target.
* @param {Function} [options.setter=null] Set value to target.
* @return {module:zrender/animation/Animation~Animator}
*/
// TODO Gap
animate: function (target, options) {
options = options || {};
var animator = new Animator(target, options.loop, options.getter, options.setter);
this.addAnimator(animator);
return animator;
}
};
util.mixin(Animation, Dispatcher);
export default Animation;