Skip to content

Commit 8901a10

Browse files
committed
Replace useAnimations with useAnimationController
1 parent b070b2b commit 8901a10

8 files changed

+289
-326
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ A basic hook to use Web Animations API.
9191

9292
[Examples](./stories/hooks/useAnimation.stories.tsx)
9393

94-
### useAnimations
94+
### useAnimationController
9595

96-
Same as [useAnimation](#useanimation), but it can have animation definitions more than 1.
96+
A hook to compose multiple [useAnimation](#useanimation) and orchestrate their animations.
9797

98-
[Examples](./stories/hooks/useAnimations.stories.tsx)
98+
[Examples](./stories/hooks/useAnimationController.stories.tsx)
9999

100100
### useAnimationFunction
101101

@@ -105,7 +105,7 @@ Same as [useAnimation](#useanimation), but it drives function not React element.
105105

106106
### useTransitionAnimation
107107

108-
Similar to [useAnimations](#useanimations), but it plays when element enter/update/exits.
108+
A hook to compose multiple [useAnimation](#useanimation) and plays them when element enter/update/exits.
109109
This hook must be used under [AnimationGroup component](#animationgroup).
110110

111111
[Examples](./stories/hooks/useTransitionAnimation.stories.tsx)

src/hooks/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export type {
66
PlayOptions,
77
} from "./core";
88
export { useAnimation } from "./useAnimation";
9-
export type { AnimationHandle, WithRef } from "./useAnimation";
9+
export type { AnimationHandle } from "./useAnimation";
10+
export { useAnimationController } from "./useAnimationController";
11+
export type { AnimationController } from "./useAnimationController";
1012
export { useAnimationFunction } from "./useAnimationFunction";
1113
export type {
1214
AnimationFunctionHandle,
1315
AnimationFunction,
1416
ComputedTimingContext,
1517
} from "./useAnimationFunction";
16-
export { useAnimations } from "./useAnimations";
17-
export type { AnimationsHandle } from "./useAnimations";
1818
export { useTransitionAnimation } from "./useTransitionAnimation";

src/hooks/useAnimation.ts

+87-82
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { createRef, useEffect, useLayoutEffect, useRef, useState } from "react";
1+
import {
2+
createRef,
3+
RefObject,
4+
useEffect,
5+
useLayoutEffect,
6+
useRef,
7+
useState,
8+
} from "react";
29
import { isSameObject, isSameObjectArray, toArray } from "../utils";
310
import {
411
AnimationOptions,
@@ -20,99 +27,97 @@ export type AnimationHandle = {
2027
rate: number | ((prevRate: number) => number)
2128
) => AnimationHandle;
2229
end: () => Promise<void>;
30+
ref: RefObject<any>;
2331
};
24-
export type WithRef<T> = T & { ref: React.RefObject<any> };
2532

2633
export const useAnimation = (
2734
keyframe: TypedKeyframe | TypedKeyframe[],
2835
options?: AnimationOptions
29-
): WithRef<AnimationHandle> => {
36+
): AnimationHandle => {
3037
const keyframeRef = useRef(keyframe);
3138
const optionsRef = useRef(options);
3239

33-
const [animation, cleanup] = useState<[WithRef<AnimationHandle>, () => void]>(
34-
() => {
35-
const ref = createRef<HTMLElement>();
40+
const [animation, cleanup] = useState<[AnimationHandle, () => void]>(() => {
41+
const ref = createRef<HTMLElement>();
3642

37-
const getTarget = () => ref.current;
38-
const getKeyframes = () => toArray(keyframeRef.current);
39-
const getOptions = () => optionsRef.current;
43+
const getTarget = () => ref.current;
44+
const getKeyframes = () => toArray(keyframeRef.current);
45+
const getOptions = () => optionsRef.current;
4046

41-
let cache:
42-
| [
43-
animation: Animation,
44-
el: HTMLElement,
45-
keyframes: TypedKeyframe[],
46-
options: AnimationOptions | undefined
47-
]
48-
| undefined;
49-
const initAnimation = (
50-
keyframes: TypedKeyframe[],
51-
options: AnimationOptions | undefined
52-
): Animation => {
53-
const el = getTarget()!;
54-
if (cache) {
55-
const [prevAnimation, prevEl, prevKeyframes, prevOptions] = cache;
56-
if (
57-
el === prevEl &&
58-
isSameObjectArray(keyframes, prevKeyframes) &&
59-
isSameObject(options, prevOptions)
60-
) {
61-
return prevAnimation;
62-
}
63-
prevAnimation.cancel();
47+
let cache:
48+
| [
49+
animation: Animation,
50+
el: HTMLElement,
51+
keyframes: TypedKeyframe[],
52+
options: AnimationOptions | undefined
53+
]
54+
| undefined;
55+
const initAnimation = (
56+
keyframes: TypedKeyframe[],
57+
options: AnimationOptions | undefined
58+
): Animation => {
59+
const el = getTarget()!;
60+
if (cache) {
61+
const [prevAnimation, prevEl, prevKeyframes, prevOptions] = cache;
62+
if (
63+
el === prevEl &&
64+
isSameObjectArray(keyframes, prevKeyframes) &&
65+
isSameObject(options, prevOptions)
66+
) {
67+
return prevAnimation;
6468
}
65-
const animation = createAnimation(el, keyframes as Keyframe[], options);
66-
cache = [animation, el, keyframes, options];
67-
return animation;
68-
};
69-
const getAnimation = () => cache?.[0];
70-
const handle = createHandle();
69+
prevAnimation.cancel();
70+
}
71+
const animation = createAnimation(el, keyframes as Keyframe[], options);
72+
cache = [animation, el, keyframes, options];
73+
return animation;
74+
};
75+
const getAnimation = () => cache?.[0];
76+
const handle = createHandle();
7177

72-
const externalHandle: WithRef<AnimationHandle> = {
73-
play: (opts) => {
74-
handle._play(initAnimation(getKeyframes(), getOptions()), opts);
75-
return externalHandle;
76-
},
77-
reverse: () => {
78-
handle._reverse(initAnimation(getKeyframes(), getOptions()));
79-
return externalHandle;
80-
},
81-
cancel: () => {
82-
handle._cancel(getAnimation());
83-
return externalHandle;
84-
},
85-
finish: () => {
86-
handle._finish(getAnimation());
87-
return externalHandle;
88-
},
89-
pause: () => {
90-
handle._pause(getAnimation());
91-
return externalHandle;
92-
},
93-
persist: () => {
94-
handle._persist(getAnimation(), getTarget()!, getKeyframes());
95-
return externalHandle;
96-
},
97-
setTime: (time) => {
98-
handle._setTime(getAnimation(), time);
99-
return externalHandle;
100-
},
101-
setPlaybackRate: (rate) => {
102-
handle._setRate(getAnimation(), rate);
103-
return externalHandle;
104-
},
105-
end: () => handle._end(getAnimation()),
106-
ref,
107-
};
108-
return [
109-
externalHandle,
110-
() => {
111-
handle._cancel(getAnimation());
112-
},
113-
];
114-
}
115-
)[0];
78+
const externalHandle: AnimationHandle = {
79+
play: (opts) => {
80+
handle._play(initAnimation(getKeyframes(), getOptions()), opts);
81+
return externalHandle;
82+
},
83+
reverse: () => {
84+
handle._reverse(initAnimation(getKeyframes(), getOptions()));
85+
return externalHandle;
86+
},
87+
cancel: () => {
88+
handle._cancel(getAnimation());
89+
return externalHandle;
90+
},
91+
finish: () => {
92+
handle._finish(getAnimation());
93+
return externalHandle;
94+
},
95+
pause: () => {
96+
handle._pause(getAnimation());
97+
return externalHandle;
98+
},
99+
persist: () => {
100+
handle._persist(getAnimation(), getTarget()!, getKeyframes());
101+
return externalHandle;
102+
},
103+
setTime: (time) => {
104+
handle._setTime(getAnimation(), time);
105+
return externalHandle;
106+
},
107+
setPlaybackRate: (rate) => {
108+
handle._setRate(getAnimation(), rate);
109+
return externalHandle;
110+
},
111+
end: () => handle._end(getAnimation()),
112+
ref,
113+
};
114+
return [
115+
externalHandle,
116+
() => {
117+
handle._cancel(getAnimation());
118+
},
119+
];
120+
})[0];
116121

117122
useLayoutEffect(() => {
118123
keyframeRef.current = keyframe;

src/hooks/useAnimationController.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {
2+
MutableRefObject,
3+
RefCallback,
4+
useEffect,
5+
useLayoutEffect,
6+
useRef,
7+
useState,
8+
} from "react";
9+
import { getKeys } from "../utils";
10+
import type { PlayOptions } from "./core";
11+
import type { AnimationHandle } from "./useAnimation";
12+
13+
export type AnimationController<ID extends string> = {
14+
get: (name: ID) => AnimationHandle;
15+
playAll: (opts?: PlayOptions) => AnimationController<ID>;
16+
reverseAll: () => AnimationController<ID>;
17+
cancelAll: () => AnimationController<ID>;
18+
finishAll: () => AnimationController<ID>;
19+
pauseAll: () => AnimationController<ID>;
20+
ref: RefCallback<any>;
21+
};
22+
23+
export const useAnimationController = <ID extends string>(
24+
definitions: {
25+
[key in ID]: AnimationHandle;
26+
}
27+
): AnimationController<ID> => {
28+
const definitionsRef = useRef(definitions);
29+
30+
const [animation, cleanup] = useState<[AnimationController<ID>, () => void]>(
31+
() => {
32+
const getHandle = (name: ID): AnimationHandle => {
33+
return definitionsRef.current[name];
34+
};
35+
const forAllHandle = (fn: (handle: AnimationHandle) => void) => {
36+
getKeys(definitionsRef.current).forEach((name) =>
37+
fn(getHandle(name as ID))
38+
);
39+
};
40+
41+
const externalHandle: AnimationController<ID> = {
42+
get: getHandle,
43+
playAll: (opts) => {
44+
forAllHandle((handle) => {
45+
handle.play(opts);
46+
});
47+
return externalHandle;
48+
},
49+
reverseAll: () => {
50+
forAllHandle((handle) => {
51+
handle.reverse();
52+
});
53+
return externalHandle;
54+
},
55+
cancelAll: () => {
56+
forAllHandle((handle) => {
57+
handle.cancel();
58+
});
59+
return externalHandle;
60+
},
61+
finishAll: () => {
62+
forAllHandle((handle) => {
63+
handle.finish();
64+
});
65+
return externalHandle;
66+
},
67+
pauseAll: () => {
68+
forAllHandle((handle) => {
69+
handle.pause();
70+
});
71+
return externalHandle;
72+
},
73+
ref: (v) => {
74+
forAllHandle((h) => {
75+
(h.ref as MutableRefObject<any>).current = v;
76+
});
77+
},
78+
};
79+
return [
80+
externalHandle,
81+
() => {
82+
externalHandle.cancelAll();
83+
},
84+
];
85+
}
86+
)[0];
87+
88+
useLayoutEffect(() => {
89+
definitionsRef.current = definitions;
90+
});
91+
92+
useEffect(() => cleanup, []);
93+
94+
return animation;
95+
};

0 commit comments

Comments
 (0)