142 lines
3.6 KiB
TypeScript
142 lines
3.6 KiB
TypeScript
/**
|
||
* 动画主控制器
|
||
* @config target 动画对象,可以是数组,如果是数组的话会批量分发onframe等事件
|
||
* @config life(1000) 动画时长
|
||
* @config delay(0) 动画延迟时间
|
||
* @config loop(true)
|
||
* @config onframe
|
||
* @config easing(optional)
|
||
* @config ondestroy(optional)
|
||
* @config onrestart(optional)
|
||
*
|
||
* TODO pause
|
||
*/
|
||
|
||
import easingFuncs, {AnimationEasing} from './easing';
|
||
import type Animation from './Animation';
|
||
import { isFunction, noop } from '../core/util';
|
||
import { createCubicEasingFunc } from './cubicEasing';
|
||
|
||
type OnframeCallback = (percent: number) => void;
|
||
type ondestroyCallback = () => void
|
||
type onrestartCallback = () => void
|
||
|
||
export type DeferredEventTypes = 'destroy' | 'restart'
|
||
// type DeferredEventKeys = 'ondestroy' | 'onrestart'
|
||
|
||
export interface ClipProps {
|
||
life?: number
|
||
delay?: number
|
||
loop?: boolean
|
||
easing?: AnimationEasing
|
||
|
||
onframe?: OnframeCallback
|
||
ondestroy?: ondestroyCallback
|
||
onrestart?: onrestartCallback
|
||
}
|
||
|
||
export default class Clip {
|
||
|
||
private _life: number
|
||
private _delay: number
|
||
|
||
private _inited: boolean = false
|
||
private _startTime = 0 // 开始时间单位毫秒
|
||
|
||
private _pausedTime = 0
|
||
private _paused = false
|
||
|
||
animation: Animation
|
||
|
||
loop: boolean
|
||
|
||
easing: AnimationEasing
|
||
easingFunc: (p: number) => number
|
||
|
||
// For linked list. Readonly
|
||
next: Clip
|
||
prev: Clip
|
||
|
||
onframe: OnframeCallback
|
||
ondestroy: ondestroyCallback
|
||
onrestart: onrestartCallback
|
||
|
||
constructor(opts: ClipProps) {
|
||
|
||
this._life = opts.life || 1000;
|
||
this._delay = opts.delay || 0;
|
||
|
||
this.loop = opts.loop || false;
|
||
|
||
this.onframe = opts.onframe || noop;
|
||
this.ondestroy = opts.ondestroy || noop;
|
||
this.onrestart = opts.onrestart || noop;
|
||
|
||
opts.easing && this.setEasing(opts.easing);
|
||
}
|
||
|
||
step(globalTime: number, deltaTime: number): boolean {
|
||
// Set startTime on first step, or _startTime may has milleseconds different between clips
|
||
// PENDING
|
||
if (!this._inited) {
|
||
this._startTime = globalTime + this._delay;
|
||
this._inited = true;
|
||
}
|
||
|
||
if (this._paused) {
|
||
this._pausedTime += deltaTime;
|
||
return;
|
||
}
|
||
|
||
const life = this._life;
|
||
let elapsedTime = globalTime - this._startTime - this._pausedTime;
|
||
let percent = elapsedTime / life;
|
||
|
||
// PENDING: Not begin yet. Still run the loop.
|
||
// In the case callback needs to be invoked.
|
||
// Or want to update to the begin state at next frame when `setToFinal` and `delay` are both used.
|
||
// To avoid the unexpected blink.
|
||
if (percent < 0) {
|
||
percent = 0;
|
||
}
|
||
|
||
percent = Math.min(percent, 1);
|
||
|
||
const easingFunc = this.easingFunc;
|
||
const schedule = easingFunc ? easingFunc(percent) : percent;
|
||
|
||
this.onframe(schedule);
|
||
|
||
// 结束
|
||
if (percent === 1) {
|
||
if (this.loop) {
|
||
// Restart
|
||
const remainder = elapsedTime % life;
|
||
this._startTime = globalTime - remainder;
|
||
this._pausedTime = 0;
|
||
|
||
this.onrestart();
|
||
}
|
||
else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
pause() {
|
||
this._paused = true;
|
||
}
|
||
|
||
resume() {
|
||
this._paused = false;
|
||
}
|
||
|
||
setEasing(easing: AnimationEasing) {
|
||
this.easing = easing;
|
||
this.easingFunc = isFunction(easing)
|
||
? easing
|
||
: easingFuncs[easing] || createCubicEasingFunc(easing);
|
||
}
|
||
} |