-
Notifications
You must be signed in to change notification settings - Fork 170
/
Copy pathAnimationUtils.ts
114 lines (105 loc) · 3.54 KB
/
AnimationUtils.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
import { Binding } from '../../components/Binding';
import { Frame } from '../../components/Frame';
import { Visible } from '../../components/Visible';
import { AnimationConfig } from './Animatable';
export function getNodeDimensions(item: Visible) {
return {
height: item.height(),
width: item.width()
};
}
export function getNodeLocation(item: Visible) {
return {
x: item.x(),
y: item.y()
};
}
export function getNodePosition(item: Visible) {
return {
...getNodeDimensions(item),
...getNodeLocation(item)
};
}
// Given a current frame, find a binding by traversing the enclosing environments (frames).
export function lookupBinding(currFrame: Frame, bindingName: string): [Frame, Binding] {
let frame: Frame | undefined = currFrame;
while (frame !== undefined) {
const binding = frame.bindings.find(b => b.keyString.split(':')[0] === bindingName);
// return the top most global frame if we have reached the top of the tree
if (frame?.environment?.id === '-1') {
return [frame, binding ?? frame.bindings[0]];
}
if (binding) {
return [frame, binding];
}
frame = frame.parentFrame;
}
// this line should never be reached as long as the interpreter works correctly
throw new Error(
`Error: Binding with name "${bindingName}" cannot be found within the environment!`
);
}
/** Simple check for if a string is a hex color string. Does not check individual characters. */
function isHexColor(value: string): boolean {
return value.startsWith('#') && (value.length === 4 || value.length === 7);
}
/** Converts a hex color string to a 3-element rgb array */
function parseHexColor(color: string): [number, number, number] {
if (!isHexColor(color)) {
throw new Error(`Cannot parse given color string: ${color}`);
}
if (color.length === 4) color = color + color.slice(1);
return [
parseInt(color.slice(1, 3), 16),
parseInt(color.slice(3, 5), 16),
parseInt(color.slice(5, 7), 16)
];
}
/** Converts a rgb array to hex color string */
function convertRgbToHex([r, g, b]: [number, number, number]): `#${string}` {
return `#${
r.toString(16).padStart(2, '0') +
g.toString(16).padStart(2, '0') +
b.toString(16).padStart(2, '0')
}`;
}
/**
* Interpolates between the colors based on the given delta and easing function.
* Only supports hex colors, e.g. `#000`, `#abcdef`, etc.
*/
function lerpColor(
delta: number,
startColor: string | undefined,
endColor: string,
easingFn: NonNullable<AnimationConfig['easing']>
): `#${string}` {
if (!startColor) return endColor as `#${string}`;
const startRgb = parseHexColor(startColor);
const endRgb = parseHexColor(endColor);
const rgb = Array.from({ length: 3 }, (_, i) =>
Math.round(easingFn(delta, startRgb[i], endRgb[i] - startRgb[i], 1))
);
return convertRgbToHex(rgb as [number, number, number]);
}
/**
* Interpolates between `startValue` and `endValue` at the given `delta`, depending on the given
* `property` and `easingFn`.
* Only supports interpolation between numbers, or color strings
*/
export function lerp(
delta: number,
property: string,
startValue: any,
endValue: any,
easingFn: NonNullable<AnimationConfig['easing']>
): any {
if (startValue === endValue) return endValue;
if (typeof endValue === 'number') {
if (typeof startValue !== 'number') return endValue;
return easingFn(delta, startValue, endValue - startValue, 1);
} else if (property === 'fill' || property === 'stroke') {
return lerpColor(delta, startValue, endValue, easingFn);
} else {
return endValue;
}
}