forked from infernojs/inferno
-
Notifications
You must be signed in to change notification settings - Fork 0
/
animationCoordinator.ts
145 lines (132 loc) · 5.09 KB
/
animationCoordinator.ts
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
143
144
145
import { forceReflow } from './utils';
// This is only used for development and should be set to false for release
const _DBG_COORD_ = false && process.env.NODE_ENV !== 'production';
export const enum AnimationPhase {
INITIALIZE,
MEASURE,
SET_START_STATE,
ACTIVATE_TRANSITIONS,
REGISTER_LISTENERS,
ACTIVATE_ANIMATION,
length // This will equal length of actual phases since TS converts this to a zero based list of ints
}
let _animationQueue: Function[] = [];
let _animationActivationQueue: Function[] = [];
const IDLE = 0;
let _nextAnimationFrame: number = IDLE;
let _nextActivateAnimationFrame: number = IDLE;
function _runActivateAnimationPhase() {
_nextActivateAnimationFrame = IDLE;
// Get animations to execute
const animationQueue = _animationActivationQueue;
// Clear global queue
_animationActivationQueue = [];
for (let i = 0; i < animationQueue.length; i++) {
animationQueue[i](AnimationPhase.ACTIVATE_ANIMATION);
}
}
function _runAnimationPhases() {
_nextAnimationFrame = IDLE;
// Get animations to execute
const animationQueue = _animationQueue;
// Clear global queue
_animationQueue = [];
// So what this does is run the animation phases in order. Most of the phases are invoked
// by a simple call to all the registered callbacks. However:
//
// - ACTIVATE_TRANSITIONS require a reflow in order to not
// interfere with the previous setting of the animation start class
//
// - ACTIVATE_ANIMATION needs to be called async so the transitions actually fire,
// we choose to use an animation frame.
//
for (let i = 0; i < AnimationPhase.length; i++) {
const phase = i as AnimationPhase;
switch (phase) {
case AnimationPhase.ACTIVATE_ANIMATION:
// Final phase - Activate animations
// This is a special case and is executed differently from others
_animationActivationQueue = _animationActivationQueue.concat(animationQueue);
if (_nextActivateAnimationFrame === IDLE) {
// Animations are activated on the next animation frame
_nextActivateAnimationFrame = requestAnimationFrame(_runActivateAnimationPhase);
}
break;
default:
if (phase === AnimationPhase.ACTIVATE_TRANSITIONS) {
// Force reflow before executing ACTIVATE_TRANSITIONS
forceReflow();
}
for (let j = 0; j < animationQueue.length; j++) {
animationQueue[j](phase);
}
}
}
}
function _debugAnimationPhases(phase: AnimationPhase, animationQueue) {
// When debugging we call _runAnimationPhases once for each phase
// so only set to idle when done
if (phase === AnimationPhase.length - 1) {
_nextAnimationFrame = IDLE;
}
switch (phase) {
case AnimationPhase.ACTIVATE_ANIMATION:
// Final phase - Activate animations
// This is a special case and is executed differently from others
_animationActivationQueue = _animationActivationQueue.concat(animationQueue);
if (_nextActivateAnimationFrame === IDLE) {
// Animations are activated on the next animation frame
_nextActivateAnimationFrame = requestAnimationFrame(_runActivateAnimationPhase);
}
break;
default:
if (phase === AnimationPhase.ACTIVATE_TRANSITIONS) {
// Force reflow before executing ACTIVATE_TRANSITIONS
forceReflow();
}
for (let j = 0; j < animationQueue.length; j++) {
animationQueue[j](phase);
}
}
return phase + 1;
}
export function queueAnimation(callback: Function) {
_animationQueue.push(callback);
if (_nextAnimationFrame === IDLE) {
if (!_DBG_COORD_) {
_nextAnimationFrame = requestAnimationFrame(_runAnimationPhases);
} else {
/**** DEV DEBUGGING code path ****/
// Run animation phases one at a time when debugging
// to allow visually inspecting changes.
let _animationDebugQueue = _animationQueue;
const _runPhase = (startPhase) => {
_nextAnimationFrame = requestAnimationFrame(() => {
// Reset the global animation queue so any changes
// added during this animation round is queued
if (_animationDebugQueue === _animationQueue) {
_animationQueue = [];
}
const nextStartPhase = _debugAnimationPhases(startPhase, _animationDebugQueue);
if (nextStartPhase !== undefined && nextStartPhase < AnimationPhase.length) {
_runPhase(nextStartPhase);
} else if (_animationQueue.length > 0) {
// All phases done, check if the queue has been repopulated
// and rerun if it has
_animationDebugQueue = _animationQueue;
_runPhase(0);
}
});
};
// TODO: We could create hooks to show a simply UI to control
// animation execution. For now you need to set a break point
_runPhase(0);
/**** /end DEV DEBUGGING ****/
}
}
}
// This is needed for tests. Coordinated animations are run on
// next animation frame so we need to make sure we wait for them to finish.
export function hasPendingAnimations() {
return _nextAnimationFrame !== IDLE || _nextActivateAnimationFrame !== IDLE;
}