zhangjian
2023-06-05 0976d2d0f90cff460cedfdc8bd74e98c2c31a58c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
 * 动画主控制器
 * @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);
    }
}