+32
-33
src/useTransition.ts
+32
-33
src/useTransition.ts
···
2
2
import { useState, useCallback } from 'react';
3
3
import { useLayoutEffect } from './utils/react';
4
4
5
-
interface AnimationState {
6
-
animation: Animation;
7
-
to: Keyframe;
8
-
}
9
-
10
-
const animations = new WeakMap<HTMLElement, AnimationState>();
5
+
const animations = new WeakMap<HTMLElement, Animation>();
11
6
12
7
export interface TransitionOptions {
13
-
style: Style;
8
+
style?: Style | null;
14
9
duration?: number | string;
15
10
easing?: string | [number, number, number, number];
16
11
}
17
12
18
13
const animate = (element: HTMLElement, options: TransitionOptions) => {
19
-
const prevState = animations.get(element);
20
-
const prevTo = prevState ? prevState.to : {};
14
+
const style = options.style || {};
21
15
const computed = getComputedStyle(element);
22
16
const from: Keyframe = {};
23
17
const to: Keyframe = {};
24
18
25
-
let changed = !prevState;
26
-
for (const propName in options.style) {
27
-
let value: string = options.style[propName];
19
+
for (const propName in style) {
20
+
let value: string = style[propName];
28
21
if (typeof value === 'number') (value as string) += 'px';
29
22
30
23
let key: string;
31
24
if (/^--/.test(propName)) {
32
25
key = propName;
33
26
from[key] = element.style.getPropertyValue(propName);
34
-
element.style.setProperty(key, (to[key] = options.style[propName]));
27
+
element.style.setProperty(key, (to[key] = value));
35
28
} else {
36
-
if (propName === 'float') {
37
-
key = 'cssFloat';
38
-
} else if (propName === 'offset') {
39
-
key = 'cssOffset';
40
-
} else if (propName === 'transform') {
29
+
if (propName === 'transform') {
41
30
key = propName;
42
31
value =
43
32
('' + value || '').replace(/\w+\((?:0\w*\s*)+\)\s*/g, '') || 'none';
···
48
37
from[key] = computed[key];
49
38
element.style[key] = to[key] = value;
50
39
}
51
-
52
-
changed = changed || prevState!.to[key] !== to[key];
53
40
}
54
-
55
-
if (!changed && Object.keys(to).length === Object.keys(prevTo).length) return;
56
41
57
42
const effect: KeyframeEffectOptions = {
58
43
duration:
59
44
typeof options.duration === 'number'
60
45
? options.duration * 1000
61
-
: options.duration,
46
+
: options.duration || 1000,
62
47
easing: Array.isArray(options.easing)
63
48
? `cubic-bezier(${options.easing.join(', ')})`
64
-
: options.easing,
49
+
: options.easing || 'ease',
65
50
};
66
51
67
-
if (prevState) prevState.animation.cancel();
52
+
const prevAnimation = animations.get(element);
53
+
if (prevAnimation) prevAnimation.cancel();
68
54
69
55
const animation = element.animate([from, to], effect);
70
56
animation.playbackRate = 1.000001;
···
88
74
}
89
75
90
76
return new Promise<unknown>((resolve, reject) => {
91
-
animations.set(element, { animation, to });
77
+
animations.set(element, animation);
92
78
animation.addEventListener('cancel', reject);
93
79
animation.addEventListener('finish', resolve);
94
80
});
···
96
82
97
83
export function useTransition<T extends HTMLElement>(
98
84
ref: Ref<T>,
99
-
options: TransitionOptions
85
+
options?: TransitionOptions
100
86
): [boolean, (options: TransitionOptions) => Promise<void>] {
101
-
const [animating, setAnimating] = useState(false);
87
+
if (!options) options = {};
88
+
89
+
const style = options.style || {};
90
+
const [state, setState] = useState<[boolean, Style]>([false, style]);
91
+
if (JSON.stringify(style) !== JSON.stringify(state[1])) {
92
+
setState([true, style]);
93
+
}
102
94
103
95
const animateTo = useCallback(
104
96
(options: TransitionOptions) => {
97
+
const updateAnimating = (animating: boolean) => {
98
+
setState(state =>
99
+
state[0] !== animating ? [animating, state[1]] : state
100
+
);
101
+
};
102
+
105
103
const animation = animate(ref.current!, options);
106
104
if (animation) {
107
-
setAnimating(true);
105
+
updateAnimating(true);
108
106
return animation
109
107
.then(() => {
110
-
setAnimating(false);
108
+
updateAnimating(false);
111
109
})
112
110
.catch(() => {});
113
111
} else {
112
+
updateAnimating(false);
114
113
return Promise.resolve();
115
114
}
116
115
},
···
118
117
);
119
118
120
119
useLayoutEffect(() => {
121
-
animateTo(options);
122
-
}, [animateTo, options.style]);
120
+
animateTo(options!);
121
+
}, [animateTo, state[1]]);
123
122
124
-
return [animating, animateTo];
123
+
return [state[0], animateTo];
125
124
}