Skip to content

Commit c152ddb

Browse files
authored
Merge pull request #464 from wellyshen/feature/set-animation
Cache options on mount & refine types
2 parents ecf6be4 + d1fe1be commit c152ddb

File tree

7 files changed

+80
-103
lines changed

7 files changed

+80
-103
lines changed

README.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Using [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_
3434
- 🔩 Supports custom `refs` for [some reasons](#use-your-own-ref).
3535
- 📜 Supports [TypeScript](https://www.typescriptlang.org) type definition.
3636
- 🗄️ Server-side rendering compatibility.
37-
- 🦔 Tiny size ([~ 4.8KB gzipped](https://bundlephobia.com/result?p=@wellyshen/use-web-animations)).
37+
- 🦔 Tiny size ([~ 3.8KB gzipped](https://bundlephobia.com/result?p=@wellyshen/use-web-animations)). No external dependencies, aside for the `react`.
3838

3939
## Requirement
4040

@@ -129,7 +129,22 @@ const App = () => {
129129
};
130130
```
131131

132-
> 💡 By default, the hook will be updated when options changed (i.e. keyframes, animationOptions etc.). However, you can disable the behavior by setting the `shouldUpdateAnimation` option to `false`.
132+
### Setting/Updating Animation
133+
134+
The `keyframes` and `animationOptions` are cached when the hook is mounted. However, we can set/update the animation by the `animation` method.
135+
136+
```js
137+
const { animation } = useWebAnimations();
138+
139+
const changeAnim = () =>
140+
animation({
141+
keyframes: { transform: ["translateX(0)", "translateX(100px)"] },
142+
animationOptions: 1000,
143+
id: "123",
144+
playbackRate: 1,
145+
autoPlay: true,
146+
});
147+
```
133148

134149
### Playback Control
135150

@@ -528,24 +543,24 @@ It's returned with the following properties.
528543
| `ref` | object | | Used to set the target element for animating. |
529544
| `playState` | string | | Describes the playback state of an animation. |
530545
| `getAnimation` | function | | Access the [animation instance](https://developer.mozilla.org/en-US/docs/Web/API/Animation) for [playback control](#playback-control), [animation's information](#getting-animations-information) and more. |
531-
| `animate` | function | | Creates animation at the `animationOptions` you want. Useful for [interactive animations and composite animations](#dynamic-interactions-with-animation). |
546+
| `animate` | function | | Imperatively [set/update the animation](#settingupdating-animation). Useful for [interactive animations and composite animations](#dynamic-interactions-with-animation). |
532547

533548
### Parameter
534549

535550
The `options` provides the following configurations and event callbacks for you.
536551

537-
| Key | Type | Default | Description |
538-
| ----------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
539-
| `ref` | object | | For [some reasons](#use-your-own-ref), you can pass in your own ref instead of using the built-in. |
540-
| `id` | string | `""` | Sets the ID of an animation, implemented based on the [Animation.id](https://developer.mozilla.org/en-US/docs/Web/API/Animation/id). |
541-
| `playbackRate` | number | `1` | Sets the playback rate of an animation, implemented based on the [Animation.playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate). |
542-
| `autoPlay` | boolean | `true` | Automatically starts the animation. |
543-
| `keyframes` | Array \| object | | An array of keyframe objects, or a keyframe object whose property are arrays of values to iterate over. See [basic usage](#basic-usage) for more details. |
544-
| `animationOptions` | number \| object | | An **integer** representing the animation's duration (in milliseconds), or an **object** containing one or more timing properties. See [basic usage](#basic-usage) for more details. |
545-
| `shouldUpdateAnimation` | boolean | `true` | By default, the hook will be updated when options changed (i.e. keyframes, animationOptions etc.). However, you can disable the behavior by setting this option to `false`. |
546-
| `onReady` | function | | It's invoked when an animation is ready to play. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
547-
| `onUpdate` | function | | It's invoked when an animation enters the `running` state or changes state. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
548-
| `onFinish` | function | | It's invoked when an animation enters the `finished` state. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
552+
| Key | Type | Default | Description |
553+
| ------------------ | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
554+
| `ref` | object | | For [some reasons](#use-your-own-ref), you can pass in your own ref instead of using the built-in. |
555+
| `id` | string | `""` | Sets the ID of an animation, implemented based on the [Animation.id](https://developer.mozilla.org/en-US/docs/Web/API/Animation/id). |
556+
| `playbackRate` | number | `1` | Sets the playback rate of an animation, implemented based on the [Animation.playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate). |
557+
| `autoPlay` | boolean | `true` | Automatically starts the animation. |
558+
| `keyframes` | Array \| object | | An array of keyframe objects, or a keyframe object whose property are arrays of values to iterate over. See [basic usage](#basic-usage) for more details. |
559+
| `animationOptions` | number \| object | | An **integer** representing the animation's duration (in milliseconds), or an **object** containing one or more timing properties. See [basic usage](#basic-usage) for more details. |
560+
561+
| `onReady` | function | | It's invoked when an animation is ready to play. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
562+
| `onUpdate` | function | | It's invoked when an animation enters the `running` state or changes state. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
563+
| `onFinish` | function | | It's invoked when an animation enters the `finished` state. You can access the [playState](#basic-usage), [animate](#dynamic-interactions-with-animation) and [animation](#getting-animations-information) from the event object. |
549564

550565
## Use Polyfill
551566

demo/Animations/index.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1-
import { FC, ChangeEvent, useState } from "react";
1+
import { FC, ChangeEvent } from "react";
22

33
import useWebAnimations from "../../src";
44
import * as animations from "../../src/animations";
55
import { container as sharedContainer, title, subtitle } from "../theme";
66
import { container, link, target, select } from "./styles";
77

88
const Animations: FC = () => {
9-
const [val, setVal] = useState<string>("bounce");
10-
// @ts-expect-error
11-
const { keyframes, animationOptions } = animations[val];
12-
const { ref, getAnimation } = useWebAnimations<HTMLDivElement>({
13-
keyframes,
14-
animationOptions: { ...animationOptions, fill: "auto" },
9+
const { bounce } = animations;
10+
const { ref, getAnimation, animate } = useWebAnimations<HTMLDivElement>({
11+
keyframes: bounce.keyframes,
12+
animationOptions: { ...bounce.animationOptions, fill: "auto" },
1513
});
1614

1715
const play = () => {
1816
// @ts-expect-error
1917
getAnimation().play();
2018
};
2119

22-
const handleChangeSelect = (e: ChangeEvent<HTMLSelectElement>) => {
23-
setVal(e.currentTarget.value);
20+
const handleChangeSelect = ({
21+
currentTarget,
22+
}: ChangeEvent<HTMLSelectElement>) => {
23+
// @ts-expect-error
24+
const { keyframes, animationOptions } = animations[currentTarget.value];
25+
26+
animate({
27+
keyframes,
28+
animationOptions: { ...animationOptions, fill: "auto" },
29+
});
2430
};
2531

2632
return (

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@
6060
"**/*": "prettier -w -u"
6161
},
6262
"dependencies": {
63-
"@babel/runtime": "^7.14.0",
64-
"use-deep-compare-effect": "^1.6.1"
63+
"@babel/runtime": "^7.14.0"
6564
},
6665
"devDependencies": {
6766
"@babel/core": "^7.14.3",

src/__tests__/useWebAnimations.ts

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,10 @@ describe("useWebAnimations", () => {
2222
ref = target,
2323
keyframes = mockKeyframes,
2424
animationOptions = mockTiming,
25-
shouldUpdateAnimation = true,
2625
...rest
2726
}: Partial<Options<HTMLDivElement>> = {}) =>
28-
renderHook(
29-
(config) => useWebAnimations({ ...config, shouldUpdateAnimation }),
30-
{
31-
initialProps: {
32-
ref,
33-
keyframes,
34-
animationOptions,
35-
...rest,
36-
},
37-
}
27+
renderHook(() =>
28+
useWebAnimations({ ref, keyframes, animationOptions, ...rest })
3829
);
3930

4031
const e = { playState: "pause" };
@@ -53,32 +44,6 @@ describe("useWebAnimations", () => {
5344
el.animate = jest.fn(() => animation);
5445
});
5546

56-
it("should update animation", () => {
57-
const { rerender } = renderHelper();
58-
expect(el.animate).toHaveBeenCalledWith(mockKeyframes, 3000);
59-
const elm = document.createElement("div");
60-
// @ts-expect-error
61-
elm.animate = jest.fn(() => animation);
62-
const keyframes = { transform: ["translateX(100px)"] };
63-
const animationOptions = 5000;
64-
rerender({ ref: { current: elm }, keyframes, animationOptions });
65-
expect(elm.animate).toHaveBeenCalledWith(keyframes, animationOptions);
66-
});
67-
68-
it("should not update animation", () => {
69-
const { rerender } = renderHelper({ shouldUpdateAnimation: false });
70-
expect(el.animate).toHaveBeenCalledWith(mockKeyframes, 3000);
71-
const elm = document.createElement("div");
72-
// @ts-expect-error
73-
elm.animate = jest.fn(() => animation);
74-
rerender({
75-
ref: { current: elm },
76-
keyframes: { transform: ["translateX(100px)"] },
77-
animationOptions: 5000,
78-
});
79-
expect(elm.animate).not.toHaveBeenCalled();
80-
});
81-
8247
it("should cancel animation", async () => {
8348
const { unmount } = renderHelper();
8449
unmount();
@@ -192,10 +157,10 @@ describe("useWebAnimations", () => {
192157
it("should return workable ref", () => {
193158
// @ts-expect-error
194159
const { result } = renderHelper({ ref: null });
195-
expect(result.current.ref).toStrictEqual({ current: null });
160+
expect(result.current.ref).toEqual({ current: null });
196161

197162
result.current.ref = target;
198-
expect(result.current.ref).toStrictEqual(target);
163+
expect(result.current.ref).toEqual(target);
199164
});
200165

201166
it("should return playState correctly", () => {
@@ -212,7 +177,7 @@ describe("useWebAnimations", () => {
212177

213178
it("should return workable getAnimation method", () => {
214179
const { result } = renderHelper();
215-
expect(result.current.getAnimation()).toStrictEqual(animation);
180+
expect(result.current.getAnimation()).toEqual(animation);
216181
});
217182

218183
it("should return workable animate method", () => {

src/use-web-animations.d.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@ declare module "@wellyshen/use-web-animations" {
55

66
export type PlayState = string | null;
77

8-
type AnimConf = Partial<{
8+
type BaseOptions = Partial<{
99
id: string;
1010
playbackRate: number;
1111
autoPlay: boolean;
1212
animationOptions:
1313
| number
1414
| (KeyframeAnimationOptions & { pseudoElement?: string });
15-
shouldUpdateAnimation: boolean;
1615
}>;
1716

17+
export type AnimateOptions = BaseOptions & { keyframes: Keyframes };
18+
1819
export interface Animate {
19-
(args: AnimConf & { keyframes: Keyframes }): void;
20+
(options: AnimateOptions): void;
2021
}
2122

2223
export interface Event {
@@ -29,18 +30,22 @@ declare module "@wellyshen/use-web-animations" {
2930
(event: Event): void;
3031
}
3132

32-
export interface Options<T> extends AnimConf {
33+
export interface Options<T> extends BaseOptions {
3334
ref?: RefObject<T>;
34-
keyframes: Keyframes;
35+
keyframes?: Keyframes;
3536
onReady?: Callback;
3637
onUpdate?: Callback;
3738
onFinish?: Callback;
3839
}
3940

41+
export interface GetAnimation {
42+
(): Animation | undefined;
43+
}
44+
4045
export interface Return<T> {
4146
ref: RefObject<T>;
4247
playState: PlayState;
43-
getAnimation: () => Animation | undefined;
48+
getAnimation: GetAnimation;
4449
animate: Animate;
4550
}
4651

src/useWebAnimations.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { RefObject, useState, useRef, useCallback, useEffect } from "react";
2-
import useDeepCompareEffect from "use-deep-compare-effect";
32

43
import useLatest from "./useLatest";
54

@@ -10,17 +9,16 @@ export const eventErr = (type: string): string =>
109

1110
type Keyframes = Keyframe[] | PropertyIndexedKeyframes;
1211
type PlayState = string | null;
13-
type AnimConf = Partial<{
12+
type BaseOptions = Partial<{
1413
id: string;
1514
playbackRate: number;
1615
autoPlay: boolean;
1716
animationOptions:
1817
| number
1918
| (KeyframeAnimationOptions & { pseudoElement?: string });
20-
shouldUpdateAnimation: boolean;
2119
}>;
2220
interface Animate {
23-
(args: AnimConf & { keyframes: Keyframes }): void;
21+
(options: BaseOptions & { keyframes: Keyframes }): void;
2422
}
2523
interface Callback {
2624
(event: {
@@ -29,9 +27,9 @@ interface Callback {
2927
animation: Animation;
3028
}): void;
3129
}
32-
export interface Options<T> extends AnimConf {
30+
export interface Options<T> extends BaseOptions {
3331
ref?: RefObject<T>;
34-
keyframes: Keyframes;
32+
keyframes?: Keyframes;
3533
onReady?: Callback;
3634
onUpdate?: Callback;
3735
onFinish?: Callback;
@@ -50,16 +48,17 @@ const useWebAnimations = <T extends HTMLElement>({
5048
autoPlay,
5149
keyframes,
5250
animationOptions,
53-
shouldUpdateAnimation = true,
5451
onReady,
5552
onUpdate,
5653
onFinish,
57-
}: Options<T>): Return<T> => {
54+
}: Options<T> = {}): Return<T> => {
5855
const [playState, setPlayState] = useState<PlayState>(null);
5956
const hasUnmountedRef = useRef(false);
6057
const animRef = useRef<Animation>();
6158
const prevPendingRef = useRef<boolean>();
6259
const prevPlayStateRef = useRef<string>();
60+
const keyframesRef = useRef(keyframes);
61+
const animationOptionsRef = useRef(animationOptions);
6362
const onReadyRef = useLatest(onReady);
6463
const onUpdateRef = useLatest(onUpdate);
6564
const onFinishRef = useLatest(onFinish);
@@ -123,14 +122,16 @@ const useWebAnimations = <T extends HTMLElement>({
123122
[onFinishRef, onReadyRef, ref]
124123
);
125124

126-
useDeepCompareEffect(
127-
() => {
128-
animate({ id, playbackRate, autoPlay, keyframes, animationOptions });
129-
},
130-
shouldUpdateAnimation
131-
? [id, playbackRate, autoPlay, keyframes, animationOptions, animate]
132-
: [{}]
133-
);
125+
useEffect(() => {
126+
if (keyframesRef.current)
127+
animate({
128+
id,
129+
playbackRate,
130+
autoPlay,
131+
keyframes: keyframesRef.current,
132+
animationOptions: animationOptionsRef.current,
133+
});
134+
}, [animate, autoPlay, id, playbackRate]);
134135

135136
useEffect(() => {
136137
let rafId: number;

yarn.lock

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,7 @@
16081608
dependencies:
16091609
"@types/react" "*"
16101610

1611-
"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17.0.0", "@types/react@^17.0.8":
1611+
"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17.0.8":
16121612
version "17.0.8"
16131613
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.8.tgz#fe76e3ba0fbb5602704110fd1e3035cf394778e3"
16141614
integrity sha512-3sx4c0PbXujrYAKwXxNONXUtRp9C+hE2di0IuxFyf5BELD+B+AXL8G7QrmSKhVwKZDbv0igiAjQAMhXj8Yg3aw==
@@ -3309,11 +3309,6 @@ delayed-stream@~1.0.0:
33093309
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
33103310
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
33113311

3312-
dequal@^2.0.2:
3313-
version "2.0.2"
3314-
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
3315-
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
3316-
33173312
des.js@^1.0.0:
33183313
version "1.0.1"
33193314
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
@@ -8978,15 +8973,6 @@ url@^0.11.0:
89788973
punycode "1.3.2"
89798974
querystring "0.2.0"
89808975

8981-
use-deep-compare-effect@^1.6.1:
8982-
version "1.6.1"
8983-
resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.6.1.tgz#061a0ac5400aa0461e33dddfaa2a98bca873182a"
8984-
integrity sha512-VB3b+7tFI81dHm8buGyrpxi8yBhzYZdyMX9iBJra7SMFMZ4ci4FJ1vFc1nvChiB1iLv4GfjqaYfvbNEpTT1rFQ==
8985-
dependencies:
8986-
"@babel/runtime" "^7.12.5"
8987-
"@types/react" "^17.0.0"
8988-
dequal "^2.0.2"
8989-
89908976
use@^3.1.0:
89918977
version "3.1.1"
89928978
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"

0 commit comments

Comments
 (0)