Skip to content

Commit a61a4ce

Browse files
committed
[IMPL] useAnimation hook
1 parent b919a67 commit a61a4ce

10 files changed

Lines changed: 318 additions & 13 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useAnimation } from "../../../../../../packages/react-tools/src/hooks"
2+
3+
/**
4+
The component uses _useAnimation_ to compute an animation on p element. Use the buttons to perform action on animation.
5+
*/
6+
export const UseAnimation = () => {
7+
const { ref, playAnimation, pauseAnimation, finishAnimation, reverseAnimation } = useAnimation({
8+
keyFrames: [
9+
{ clipPath: 'circle(20% at 0% 30%)' },
10+
{ clipPath: 'circle(20% at 50% 80%)' },
11+
{ clipPath: 'circle(20% at 100% 30%)' },
12+
],
13+
immediate: true,
14+
opts: {
15+
duration: 3000,
16+
iterations: 2,
17+
direction: 'alternate',
18+
easing: 'cubic-bezier(0.46, 0.03, 0.52, 0.96)',
19+
}
20+
});
21+
22+
return <div>
23+
<p ref={ref} style={{ color: '#d23e49', fontSize: '3rem', lineHeight: 1, fontWeight: 800, display: 'inline-flex', padding: '0 5rem' }}>useAnimate</p>
24+
<div style={{ display: 'flex', justifyContent: "center", gap: 20 }}>
25+
<button onClick={playAnimation}>Play</button>
26+
<button onClick={pauseAnimation}>Pause</button>
27+
<button onClick={reverseAnimation}>Reverse</button>
28+
<button onClick={finishAnimation}>Finish</button>
29+
</div>
30+
</div>
31+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ export const COMPONENTS = [
105105
"usePIP",
106106
"useDocumentPIP",
107107
"usePopover",
108-
"useRemotePlayback"
108+
"useRemotePlayback",
109+
"useAnimation"
109110
]
110111
],
111112
//UTILS
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# useAnimation
2+
Hook to use [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseAnimation = () => {
8+
const { ref, playAnimation, pauseAnimation, finishAnimation, reverseAnimation } = useAnimation({
9+
keyFrames: [
10+
{ clipPath: 'circle(20% at 0% 30%)' },
11+
{ clipPath: 'circle(20% at 50% 80%)' },
12+
{ clipPath: 'circle(20% at 100% 30%)' },
13+
],
14+
immediate: true,
15+
opts: {
16+
duration: 3000,
17+
iterations: 2,
18+
direction: 'alternate',
19+
easing: 'cubic-bezier(0.46, 0.03, 0.52, 0.96)',
20+
}
21+
});
22+
23+
return <div>
24+
<p ref={ref} style={{ color: '#d23e49', fontSize: '3rem', lineHeight: 1, fontWeight: 800, display: 'inline-flex', padding: '0 5rem' }}>useAnimate</p>
25+
<div style={{ display: 'flex', justifyContent: "center", gap: 20 }}>
26+
<button onClick={playAnimation}>Play</button>
27+
<button onClick={pauseAnimation}>Pause</button>
28+
<button onClick={reverseAnimation}>Reverse</button>
29+
<button onClick={finishAnimation}>Finish</button>
30+
</div>
31+
</div>
32+
}
33+
```
34+
35+
> The component uses _useAnimation_ to compute an animation on p element. Use the buttons to perform action on animation.
36+
37+
38+
## API
39+
40+
```tsx
41+
useAnimation<T extends Element>({ keyFrames, immediate, opts, onCancel, onFinish, onRemove, onError }: UseAnimationProps): UseAnimationResult<T>
42+
```
43+
44+
> ### Params
45+
>
46+
> - __param__: _UseAnimationProps_
47+
object
48+
> - __param.keyFrames__: _Keyframe[] | PropertyIndexedKeyframes | null_
49+
array of keyfram objects ot a keyframe object whose properties are arrays of values to iterate over.
50+
> - __param.immediate=false?__: _boolean_
51+
boolean to start animation immediatly or not.
52+
> - __param.opts?__: _number | KeyframeAnimationOptions_
53+
either an integer representing the animation's duration (in milliseconds), or an Object containing one or more timing properties.
54+
> - __param.onFinish?__: _(this: Animation, evt: AnimationPlaybackEvent) => void_
55+
function that will be executed when animation has been finished.
56+
> - __param.onRemove?__: _(this: Animation, evt: Event) => void_
57+
function that will be executed when animation has been removed.
58+
> - __param.onCancel?__: _(this: Animation, evt: AnimationPlaybackEvent) => void_
59+
function that will be executed when animation has been canceled.
60+
> - __param.onError?__: _(err: unknown) => void_
61+
function that will be executed when an error occurred.
62+
>
63+
64+
> ### Returns
65+
>
66+
> __result__: _UseAnimationResult_
67+
> Object with these properties:
68+
> - __isSupported__: boolean to indicate if Web Animations API is supported or not.
69+
> - __ref__: RefCallback that need to be attached to element to animate.
70+
> - __playAnimation__: function to play animation.
71+
> - __pauseAnimation__: function to pause animation.
72+
> - __finishAnimation__: function to finish animation.
73+
> - __cancelAnimation__: function to cancel animation.
74+
> - __persistAnimation__: function to persist animation.
75+
> - __reverseAnimation__: function to reverse animation.
76+
> - __commitStyles__: function that writes the computed values of the animation's current styles into its target element's style attribute.
77+
> - __updatePlaybackRate__: function that sets the speed of an animation after first synchronizing its playback position.
78+
>

apps/react-tools-demo/src/markdown/useRemotePlayback.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ Hook to use [RemotePlayback API](https://developer.mozilla.org/en-US/docs/Web/AP
55

66
```tsx
77
export const UseRemotePlayback = () => {
8-
const { isSupported, ref, prompt, state, watchAvailability } = useRemotePlayback<HTMLVideoElement>();
8+
const { isSupported, ref, prompt, state } = useRemotePlayback<HTMLVideoElement>();
99

10-
return <div>
10+
return <div style={{display: "flex", flexDirection: "column", width: 'fit-content', alignItems: "center", margin: '0 auto'}}>
1111
<p>Is supported: {isSupported ? 'Yes' : 'No'}</p>
1212
<p>Current state: {state}</p>
1313
<video ref={ref} width="400" controls>
1414
<source src={video} type="video/mp4" />
1515
Your browser does not support HTML video.
1616
</video>
17-
<button onClick={()=>watchAvailability(console.log)}>Prompt</button>
17+
<button style={{marginTop: 20}} onClick={prompt} disabled={state === "unavailable"}>Prompt</button>
1818
</div>
1919
}
2020
```
@@ -49,7 +49,5 @@ function that will be executed on error watching or cancel watching devices avai
4949
> - __ref__: ref to attach media element.
5050
> - __isSupported__: boolean that indicates if RemotePlayback API is available or not.
5151
> - __state__: remote device state: _connected_, _connecting_ or _disconnect_.
52-
> - __watchAvailability__: function that watchs remove device availability.
53-
> - __cancelWatchAvailability__: function that cancel watching device availability.
5452
> - __prompt__: function that prompts the user to select an available remote playback device and give permission for the current media to be played using that device.
5553
>

packages/react-tools/README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,7 @@
108108
- [x] useDocumentPIP
109109
- [x] usePopover
110110
- [x] useRemotePlayback
111-
- [ ] useSensor (https://developer.mozilla.org/en-US/docs/Web/API/Sensor_APIs)
112-
- [ ] useSerial (https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API)
113-
- [ ] useScrollLock (https://vueuse.org/core/useScrollLock/)
114-
- [ ] useAnimate (https://vueuse.org/core/useAnimate/)
111+
- [x] useAnimation
115112
- [ ] useAudio (???)
116113
- [ ] useVideo (???)
117114
- [ ] useEventSource (https://vueuse.org/core/useEventSource/)

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ export { usePointerLock } from './usePointerLock';
8585
export { usePIP } from './usePIP';
8686
export { useDocumentPIP } from './useDocumentPIP';
8787
export { usePopover } from './usePopover';
88-
export { useRemotePlayback } from './useRemotePlayback';
88+
export { useRemotePlayback } from './useRemotePlayback';
89+
export { useAnimation } from './useAnimation';
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { RefCallback, useCallback, useRef } from "react"
2+
import { UseAnimationProps, UseAnimationResult } from "../models";
3+
4+
/**
5+
* **`useAnimation`**: Hook to use [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
6+
* @param {UseAnimationProps} param - object
7+
* @param {Keyframe[] | PropertyIndexedKeyframes | null} param.keyFrames - array of keyfram objects ot a keyframe object whose properties are arrays of values to iterate over.
8+
* @param {boolean} [param.immediate=false] - boolean to start animation immediatly or not.
9+
* @param {number | KeyframeAnimationOptions} [param.opts] - either an integer representing the animation's duration (in milliseconds), or an Object containing one or more timing properties.
10+
* @param {(this: Animation, evt: AnimationPlaybackEvent) => void} [param.onFinish] - function that will be executed when animation has been finished.
11+
* @param {(this: Animation, evt: Event) => void} [param.onRemove] - function that will be executed when animation has been removed.
12+
* @param {(this: Animation, evt: AnimationPlaybackEvent) => void} [param.onCancel] - function that will be executed when animation has been canceled.
13+
* @param {(err: unknown) => void} [param.onError] - function that will be executed when an error occurred.
14+
* @returns {UseAnimationResult} result
15+
* Object with these properties:
16+
* - __isSupported__: boolean to indicate if Web Animations API is supported or not.
17+
* - __ref__: RefCallback that need to be attached to element to animate.
18+
* - __playAnimation__: function to play animation.
19+
* - __pauseAnimation__: function to pause animation.
20+
* - __finishAnimation__: function to finish animation.
21+
* - __cancelAnimation__: function to cancel animation.
22+
* - __persistAnimation__: function to persist animation.
23+
* - __reverseAnimation__: function to reverse animation.
24+
* - __commitStyles__: function that writes the computed values of the animation's current styles into its target element's style attribute.
25+
* - __updatePlaybackRate__: function that sets the speed of an animation after first synchronizing its playback position.
26+
*/
27+
export const useAnimation = <T extends Element>({ keyFrames, immediate, opts, onCancel, onFinish, onRemove, onError }: UseAnimationProps): UseAnimationResult<T> => {
28+
const isSupported = window && HTMLElement && 'animate' in HTMLElement.prototype;
29+
const animationRef = useRef<Animation>();
30+
const optsRef = useRef<{ running: boolean, runnedImmediatly: boolean, currentTime?: CSSNumberish | null, playBackRate?: number }>({running: false, runnedImmediatly: false});
31+
const ref = useCallback<RefCallback<T>>(node => {
32+
if (node) {
33+
animationRef.current = node.animate(keyFrames, opts);
34+
animationRef.current.oncancel = function (evt: AnimationPlaybackEvent) {
35+
optsRef.current.running = false;
36+
!!onCancel && onCancel.call(this, evt)
37+
};
38+
animationRef.current.onremove = function (evt: Event) {
39+
optsRef.current.running = false;
40+
!!onRemove && onRemove.call(this, evt)
41+
};
42+
animationRef.current.onfinish = function (evt: AnimationPlaybackEvent) {
43+
optsRef.current.running = false;
44+
!!onFinish && onFinish.call(this, evt)
45+
};
46+
if (optsRef.current.playBackRate !== undefined || optsRef.current.currentTime !== undefined) {
47+
animationRef.current.currentTime = optsRef.current.currentTime!;
48+
animationRef.current.updatePlaybackRate(optsRef.current.playBackRate!);
49+
optsRef.current = {
50+
running: true,
51+
runnedImmediatly: optsRef.current.runnedImmediatly
52+
};
53+
} else {
54+
!immediate || optsRef.current.runnedImmediatly
55+
? (optsRef.current.running = false, animationRef.current.pause())
56+
: (optsRef.current = { running: true, runnedImmediatly: true })
57+
}
58+
} else {
59+
if (animationRef.current && optsRef.current.running) {
60+
optsRef.current = {
61+
...optsRef.current,
62+
currentTime: animationRef.current?.currentTime,
63+
playBackRate: animationRef.current?.playbackRate
64+
};
65+
}
66+
animationRef.current = undefined;
67+
}
68+
}, [immediate, keyFrames, opts, onCancel, onFinish, onRemove]);
69+
70+
const cancelAnimation = useCallback(() => {
71+
try {
72+
animationRef.current && animationRef.current.cancel();
73+
} catch (error) {
74+
if(onError) {
75+
onError(error);
76+
} else {
77+
throw error;
78+
}
79+
}
80+
}, [onError]);
81+
const commitStyles = useCallback(() => {
82+
try {
83+
animationRef.current && animationRef.current.commitStyles();
84+
} catch (error) {
85+
if(onError) {
86+
onError(error);
87+
} else {
88+
throw error;
89+
}
90+
}
91+
}, [onError]);
92+
const finishAnimation = useCallback(() => {
93+
try {
94+
animationRef.current && animationRef.current.finish();
95+
} catch (error) {
96+
if(onError) {
97+
onError(error);
98+
} else {
99+
throw error;
100+
}
101+
}
102+
}, [onError]);
103+
const pauseAnimation = useCallback(() => {
104+
try {
105+
animationRef.current && animationRef.current.pause();
106+
} catch (error) {
107+
if(onError) {
108+
onError(error);
109+
} else {
110+
throw error;
111+
}
112+
}
113+
}, [onError]);
114+
const persistAnimation = useCallback(() => {
115+
try {
116+
animationRef.current && animationRef.current.persist();
117+
} catch (error) {
118+
if(onError) {
119+
onError(error);
120+
} else {
121+
throw error;
122+
}
123+
}
124+
}, [onError]);
125+
const playAnimation = useCallback(() => {
126+
try {
127+
animationRef.current && animationRef.current.play();
128+
animationRef.current && (optsRef.current.running = true);
129+
} catch (error) {
130+
if(onError) {
131+
onError(error);
132+
} else {
133+
throw error;
134+
}
135+
}
136+
}, [onError]);
137+
const reverseAnimation = useCallback(() => {
138+
try {
139+
animationRef.current && animationRef.current.reverse();
140+
} catch (error) {
141+
if(onError) {
142+
onError(error);
143+
} else {
144+
throw error;
145+
}
146+
}
147+
}, [onError]);
148+
const updatePlaybackRate = useCallback((playbackRate: number) => {
149+
try {
150+
animationRef.current && animationRef.current.updatePlaybackRate(playbackRate);
151+
} catch (error) {
152+
if(onError) {
153+
onError(error);
154+
} else {
155+
throw error;
156+
}
157+
}
158+
}, [onError]);
159+
160+
return {
161+
isSupported,
162+
ref,
163+
playAnimation,
164+
pauseAnimation,
165+
finishAnimation,
166+
cancelAnimation,
167+
persistAnimation,
168+
reverseAnimation,
169+
commitStyles,
170+
updatePlaybackRate
171+
}
172+
}

packages/react-tools/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ export type {
5454
UsePopoverProps,
5555
UsePopoverResult,
5656
UseRemotePlaybackProps,
57-
UseRemotePlaybackResult
57+
UseRemotePlaybackResult,
58+
UseAnimationProps,
59+
UseAnimationResult
5860
} from './models'
5961

6062
export {

packages/react-tools/src/models/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export type { UsePointerLockProps, UsePointerLockResult } from './usePointerLock
1616
export type { UsePIPProps, UsePIPResult } from './usePIP.model';
1717
export type { DocumentPictureInPictureEvent, DocumentPIPOptions, UseDocumentPIPProps, UseDocumentPIPResult } from './useDocumentPIP.model';
1818
export type { UsePopoverProps, UsePopoverResult } from './usePopover.model';
19-
export type { UseRemotePlaybackProps, UseRemotePlaybackResult } from './useRemotePlayback.model';
19+
export type { UseRemotePlaybackProps, UseRemotePlaybackResult } from './useRemotePlayback.model';
20+
export type { UseAnimationProps, UseAnimationResult } from './useAnimation.model';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { RefCallback } from "react";
2+
3+
export interface UseAnimationProps {
4+
keyFrames: Keyframe[] | PropertyIndexedKeyframes | null;
5+
immediate?: boolean;
6+
opts?: number | KeyframeAnimationOptions;
7+
onFinish?: (this: Animation, evt: AnimationPlaybackEvent) => void;
8+
onRemove?: (this: Animation, evt: Event) => void;
9+
onCancel?: (this: Animation, evt: AnimationPlaybackEvent) => void;
10+
onError?: (err: unknown) => void;
11+
}
12+
13+
export interface UseAnimationResult<T extends Element> {
14+
isSupported: boolean;
15+
ref: RefCallback<T>;
16+
playAnimation: () => void;
17+
pauseAnimation: () => void;
18+
finishAnimation: () => void;
19+
cancelAnimation: () => void;
20+
persistAnimation: () => void;
21+
reverseAnimation: () => void;
22+
commitStyles: () => void;
23+
updatePlaybackRate: (playbackRate: number) => void;
24+
}

0 commit comments

Comments
 (0)