-
Notifications
You must be signed in to change notification settings - Fork 33
/
animation.js
99 lines (81 loc) · 3.07 KB
/
animation.js
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
let Animation;
// Use an anonymous closure to keep the AnimationManager private
(() => {
// From here: gist.github.com/gre/1650294
const easingFunctions = {
LINEAR : t => t,
EASEINQUAD : t => t ** 2,
EASEOUTQUAD : t => t * (2 - t),
EASEINOUTQUAD : t => t < .5 ? 2 * t ** 2 : -1 + (4 - 2 * t) * t,
EASEINCUBIC : t => t ** 3,
EASEOUTCUBIC : t => --t ** 3 + 1,
EASEINOUTCUBIC : t => t < .5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
EASEINQUART : t => t ** 4,
EASEOUTQUART : t => 1 - --t ** 4,
EASEINOUTQUART : t => t < .5 ? 8 * t ** 4 : 1 - 8 * --t ** 4,
EASEINQUINT : t => t ** 5,
EASEOUTQUINT : t => 1 + --t ** 5,
EASEINOUTQUINT : t => t < .5 ? 16 * t ** 5 : 1 + 16 * --t ** 5
}
function lerp(start, end, ratio) {
return start + (end - start) * ratio;
}
class AnimationManager {
constructor() {
this.activeAnimations = [];
this.update = this.update.bind(this); // Make sure 'this' is correct when invoked by requestAnimationFrame
this.update();
}
update() {
for (const anim of this.activeAnimations)
anim.update();
this.activeAnimations = this.activeAnimations.filter(anim => !anim.complete);
requestAnimationFrame(this.update);
}
registerAnimation(anim) {
this.activeAnimations.push(anim);
}
}
const animationManager = new AnimationManager();
Animation = class {
constructor(startingValues, endingValues, duration, updateCallback, finishedCallback, easing = 'EASE_IN_OUT_QUART') {
/*
* Normalize the easing function name such that all of the following are valid:
* "EASEINOUTCUBIC"
* "EASE_IN_OUT_CUBIC"
* "ease in out cubic"
* Case insensitive, optionally allowing spaces or underscores
*/
easing = easing.toUpperCase().replace(/[^A-Z]/g, '');
this.easingFunction = easingFunctions[easing];
this.duration = duration;
this.startingValues = startingValues;
this.endingValues = endingValues;
this.values = Object.assign({}, this.startingValues);
this.startTime = performance.now();
this.complete = false;
this.updateCallback = updateCallback;
this.finishedCallback = finishedCallback;
// Register this animation so that it gets updated periodically
animationManager.registerAnimation(this);
}
update() {
if (this.complete) return;
const now = performance.now();
const elapsed = now - this.startTime;
let completionRatio = elapsed / this.duration;
this.complete = completionRatio >= 1;
completionRatio = Math.min(1, this.easingFunction(completionRatio));
for (const val in this.values)
this.values[val] = lerp(this.startingValues[val], this.endingValues[val], completionRatio);
this.updateCallback(this.values);
if (this.complete)
this.finishedCallback(this.endingValues);
}
skip() {
this.complete = true;
this.updateCallback(this.endingValues);
this.finishedCallback(this.endingValues);
}
}
})();