zrender
Version:
A lightweight graphic library providing 2d draw for Apache ECharts
267 lines (226 loc) • 5.64 kB
text/typescript
/**
* Animation main class, dispatch and manage all animation controllers
*
*/
// TODO Additive animation
// http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/
// https://developer.apple.com/videos/wwdc2014/#236
import Eventful from '../core/Eventful';
import requestAnimationFrame from './requestAnimationFrame';
import Animator from './Animator';
import Clip from './Clip';
export function getTime() {
return new Date().getTime();
}
interface Stage {
update?: () => void
}
interface AnimationOption {
stage?: Stage
}
/**
* @example
* const animation = new Animation();
* const obj = {
* x: 100,
* y: 100
* };
* animation.animate(node.position)
* .when(1000, {
* x: 500,
* y: 500
* })
* .when(2000, {
* x: 100,
* y: 100
* })
* .start();
*/
export default class Animation extends Eventful {
stage: Stage
// Use linked list to store clip
private _head: Clip
private _tail: Clip
private _running = false
private _time = 0
private _pausedTime = 0
private _pauseStart = 0
private _paused = false;
constructor(opts?: AnimationOption) {
super();
opts = opts || {};
this.stage = opts.stage || {};
}
/**
* Add clip
*/
addClip(clip: Clip) {
if (clip.animation) {
// Clip has been added
this.removeClip(clip);
}
if (!this._head) {
this._head = this._tail = clip;
}
else {
this._tail.next = clip;
clip.prev = this._tail;
clip.next = null;
this._tail = clip;
}
clip.animation = this;
}
/**
* Add animator
*/
addAnimator(animator: Animator<any>) {
animator.animation = this;
const clip = animator.getClip();
if (clip) {
this.addClip(clip);
}
}
/**
* Delete animation clip
*/
removeClip(clip: Clip) {
if (!clip.animation) {
return;
}
const prev = clip.prev;
const next = clip.next;
if (prev) {
prev.next = next;
}
else {
// Is head
this._head = next;
}
if (next) {
next.prev = prev;
}
else {
// Is tail
this._tail = prev;
}
clip.next = clip.prev = clip.animation = null;
}
/**
* Delete animation clip
*/
removeAnimator(animator: Animator<any>) {
const clip = animator.getClip();
if (clip) {
this.removeClip(clip);
}
animator.animation = null;
}
update(notTriggerFrameAndStageUpdate?: boolean) {
const time = getTime() - this._pausedTime;
const delta = time - this._time;
let clip = this._head;
while (clip) {
// Save the nextClip before step.
// So the loop will not been affected if the clip is removed in the callback
const nextClip = clip.next;
let finished = clip.step(time, delta);
if (finished) {
clip.ondestroy();
this.removeClip(clip);
clip = nextClip;
}
else {
clip = nextClip;
}
}
this._time = time;
if (!notTriggerFrameAndStageUpdate) {
// '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);
this.stage.update && this.stage.update();
}
}
_startLoop() {
const self = this;
this._running = true;
function step() {
if (self._running) {
requestAnimationFrame(step);
!self._paused && self.update();
}
}
requestAnimationFrame(step);
}
/**
* Start animation.
*/
start() {
if (this._running) {
return;
}
this._time = getTime();
this._pausedTime = 0;
this._startLoop();
}
/**
* Stop animation.
*/
stop() {
this._running = false;
}
/**
* Pause animation.
*/
pause() {
if (!this._paused) {
this._pauseStart = getTime();
this._paused = true;
}
}
/**
* Resume animation.
*/
resume() {
if (this._paused) {
this._pausedTime += getTime() - this._pauseStart;
this._paused = false;
}
}
/**
* Clear animation.
*/
clear() {
let clip = this._head;
while (clip) {
let nextClip = clip.next;
clip.prev = clip.next = clip.animation = null;
clip = nextClip;
}
this._head = this._tail = null;
}
/**
* Whether animation finished.
*/
isFinished() {
return this._head == null;
}
/**
* Creat animator for a target, whose props can be animated.
*/
// TODO Gap
animate<T>(target: T, options: {
loop?: boolean // Whether loop animation
}) {
options = options || {};
// Start animation loop
this.start();
const animator = new Animator(
target,
options.loop
);
this.addAnimator(animator);
return animator;
}
}