├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
└── workflows
│ ├── check.yml
│ └── demo.yml
├── .gitignore
├── .size-limit.json
├── .storybook
├── main.js
└── preview.js
├── LICENSE
├── README.md
├── demo.gif
├── docs
├── API.md
└── interfaces
│ ├── AnimationFunctionHandle.md
│ ├── AnimationFunctionOptions.md
│ ├── AnimationHandle.md
│ ├── AnimationOptions.md
│ ├── BaseAnimationHandle.md
│ ├── ScrollTimelineOpts.md
│ ├── TimelineDefinition.md
│ ├── TransitionAnimationHandle.md
│ ├── TransitionAnimationOptions.md
│ ├── TransitionGroupProps.md
│ ├── TypedKeyframeEffectOptions.md
│ └── ViewTimelineOpts.md
├── images
├── demo-chart.gif
└── demo-scroll.gif
├── package-lock.json
├── package.json
├── rollup.config.mjs
├── src
├── @types
│ └── index.d.ts
├── core
│ ├── index.ts
│ ├── utils.spec.ts
│ ├── utils.ts
│ ├── waapi.spec.ts
│ └── waapi.ts
├── index.ts
└── react
│ ├── components
│ ├── TransitionGroup.tsx
│ └── index.ts
│ ├── hooks
│ ├── __snapshots__
│ │ ├── useAnimation.ssr.spec.tsx.snap
│ │ └── useAnimationFunction.ssr.spec.tsx.snap
│ ├── index.ts
│ ├── state.ts
│ ├── useAnimation.ssr.spec.tsx
│ ├── useAnimation.ts
│ ├── useAnimationFunction.ssr.spec.tsx
│ ├── useAnimationFunction.ts
│ ├── useIsomorphicLayoutEffect.ts
│ ├── useLatestRef.ts
│ ├── useScrollTimeline.ts
│ ├── useStatic.ts
│ ├── useTransitionAnimation.ts
│ └── useViewTimeline.ts
│ ├── index.ts
│ └── types
│ ├── index.ts
│ └── internal.ts
├── stories
├── hooks
│ ├── useAnimation.stories.tsx
│ ├── useAnimationFunction.stories.tsx
│ ├── useScrollTimeline.stories.tsx
│ ├── useTransitionAnimation.stories.tsx
│ └── useViewTimeline.stories.tsx
└── with-libraries
│ ├── css-in-js
│ ├── emotion.stories.tsx
│ ├── linaria.stories.tsx
│ ├── styled-components.stories.tsx
│ ├── vanilla-extract.css.ts
│ └── vanilla-extract.stories.tsx
│ └── ui-components
│ ├── Ant Design.stories.tsx
│ ├── Chakra UI.stories.tsx
│ ├── Fluent UI.stories.tsx
│ ├── Material UI.stories.tsx
│ └── mantine.stories.tsx
├── tsconfig.json
├── typedoc.json
└── vitest.config.ts
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior.
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Platform:**
20 | - OS: [Windows, MacOS, Linux, Android, iOS]
21 | - Browser: [Chrome, Firefox, Safari, Edge]
22 | - Version of this package: [x.x.x]
23 |
24 | **Additional context**
25 | Add any other context about the problem here.
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: check
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | setup:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - name: Setup Node
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: "lts/*"
22 | cache: "npm"
23 |
24 | - run: npm ci
25 | - run: npm run tsc
26 | - run: npm run test
27 | - run: npm run build
28 | - run: npm run size
29 | # - run: npm run storybook:test
30 |
--------------------------------------------------------------------------------
/.github/workflows/demo.yml:
--------------------------------------------------------------------------------
1 | name: demo
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | concurrency:
8 | group: ${{ github.workflow }}-${{ github.ref }}
9 | cancel-in-progress: true
10 |
11 | jobs:
12 | deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Setup Node
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: "lts/*"
21 | cache: "npm"
22 |
23 | - run: npm ci
24 | - run: npm run storybook:build
25 |
26 | - name: Deploy
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./storybook-static
31 | publish_branch: demo
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.size-limit.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "useAnimation",
4 | "path": "lib/index.mjs",
5 | "import": "{ useAnimation }",
6 | "limit": "1.0 kB"
7 | },
8 | {
9 | "name": "useAnimationFunction",
10 | "path": "lib/index.mjs",
11 | "import": "{ useAnimationFunction }",
12 | "limit": "1.0 kB"
13 | },
14 | {
15 | "name": "useScrollTimeline",
16 | "path": "lib/index.mjs",
17 | "import": "{ useScrollTimeline }",
18 | "limit": "0.3 kB"
19 | },
20 | {
21 | "name": "useViewTimeline",
22 | "path": "lib/index.mjs",
23 | "import": "{ useViewTimeline }",
24 | "limit": "0.3 kB"
25 | },
26 | {
27 | "name": "useTransitionAnimation",
28 | "path": "lib/index.mjs",
29 | "import": "{ useTransitionAnimation }",
30 | "limit": "1.3 kB"
31 | },
32 | {
33 | "name": "TransitionGroup",
34 | "path": "lib/index.mjs",
35 | "import": "{ TransitionGroup }",
36 | "limit": "0.5 kB"
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const { VanillaExtractPlugin } = require("@vanilla-extract/webpack-plugin");
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3 |
4 | /** @type { import('@storybook/react-webpack5').StorybookConfig } */
5 | export default {
6 | stories: ["../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"],
7 | addons: ["@storybook/addon-storysource", "@storybook/addon-webpack5-compiler-swc"],
8 | framework: {
9 | name: "@storybook/react-webpack5",
10 | options: { builder: {} },
11 | },
12 | refs: {
13 | "@chakra-ui/react": {
14 | disable: true,
15 | },
16 | },
17 | webpackFinal: async (config) => {
18 | // for vanilla-extract
19 | config.plugins.push(new VanillaExtractPlugin(), new MiniCssExtractPlugin());
20 | config.module.rules.push({
21 | test: /\.vanilla-extract\.css$/i,
22 | use: [
23 | MiniCssExtractPlugin.loader,
24 | {
25 | loader: "css-loader",
26 | options: {
27 | url: false,
28 | },
29 | },
30 | ],
31 | });
32 | // for linaria
33 | config.module.rules.push({
34 | test: /\/linaria\.stories.tsx$/,
35 | use: [
36 | {
37 | loader: "@linaria/webpack-loader",
38 | options: {
39 | sourceMap: false,
40 | babelOptions: {
41 | presets: [
42 | "@babel/preset-typescript",
43 | "@babel/preset-react",
44 | "@linaria/babel-preset",
45 | ],
46 | },
47 | },
48 | },
49 | ],
50 | });
51 | return config;
52 | },
53 | swc: (config) => {
54 | return {
55 | ...config,
56 | jsc: {
57 | ...config.jsc,
58 | transform: {
59 | ...config.jsc?.tranform,
60 | react: { ...config.jsc?.tranform?.react, runtime: "automatic" },
61 | },
62 | },
63 | };
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | // // For testing web-animations-js
2 | // window.Animation = undefined;
3 | // window.KeyframeEffect = undefined;
4 | // Element.prototype.animate = undefined;
5 | // import("web-animations-js");
6 |
7 | /** @type { import('@storybook/react').Preview } */
8 | export default {
9 | parameters: {
10 | actions: { argTypesRegex: "^on[A-Z].*" },
11 | controls: {
12 | matchers: {
13 | color: /(background|color)$/i,
14 | date: /Date$/,
15 | },
16 | },
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 inokawa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-animatable
2 |
3 |   [](https://github.com/inokawa/react-animatable/actions/workflows/check.yml) [](https://github.com/inokawa/react-animatable/actions/workflows/demo.yml)
4 |
5 | Tiny(~1kB) animation hooks for [React](https://github.com/facebook/react), built on [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
6 |
7 |
8 |
9 | ## Features
10 |
11 | - Easy integration with a few lines of code. Works with any UI component libraries or CSS-in-JS libraries.
12 | - Performant animation driven by native [Web Animations API (WAAPI)](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API). Also supporting [ScrollTimeline](https://developer.mozilla.org/en-US/docs/Web/API/ScrollTimeline) and [ViewTimeline](https://developer.mozilla.org/en-US/docs/Web/API/ViewTimeline).
13 | - Fully typed with [TypeScript](https://www.typescriptlang.org/), which is stricter than TypeScript's [lib.dom.d.ts](https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts).
14 | - Tiny. Each hooks are ~1kB gzipped and [the total is ~2kB gzipped](https://bundlephobia.com/package/react-animatable).
15 | - HTML, SVG, Canvas and anything can be animated.
16 |
17 | ## Motivation
18 |
19 | Animating something in React can be complicated than we expected, even with today's popular libraries. [Web Animations API (WAAPI)](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) looks like a brand-new promising way, because it's performant, it doesn't need JS runtime, it doesn't mutate style of DOM so it will not conflict with React's state, and it will become more convenient in the future ([ScrollTimeline](https://developer.mozilla.org/en-US/docs/Web/API/ScrollTimeline) and [ViewTimeline](https://developer.mozilla.org/en-US/docs/Web/API/ViewTimeline) is an example). However using raw WAAPI with React is bit tricky and having risks of memory leak. This library is what to solve the problem.
20 |
21 | ## Demo
22 |
23 | https://inokawa.github.io/react-animatable/
24 |
25 | ## Install
26 |
27 | ```sh
28 | npm install react-animatable
29 | ```
30 |
31 | ### Requirements
32 |
33 | - react >= 16.14
34 |
35 | If you use ESM and webpack 5, use react >= 18 to avoid [Can't resolve `react/jsx-runtime` error](https://github.com/facebook/react/issues/20235).
36 |
37 | And in some legacy browsers that does not support Web Animations API, [you may need to use polyfill](#use-polyfill).
38 |
39 | If you use [ScrollTimeline](https://caniuse.com/?search=scrolltimeline) or [ViewTimeline](https://caniuse.com/?search=viewtimeline), check browser support.
40 |
41 | ## Usage
42 |
43 | 1. Define your animation with `useAnimation` hook.
44 |
45 | > The hooks accepts [canonical keyframe format objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats#syntax) and [KeyframeEffect's options](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect#parameters) as arguments, so check them before using this library.
46 |
47 | 2. Pass the return value of `useAnimation` to `ref` of element you want to control.
48 |
49 | 3. Call `play()`!
50 |
51 | ```tsx
52 | import { useEffect } from "react";
53 | import { useAnimation } from "react-animatable";
54 |
55 | export const App = () => {
56 | // 1. Define your animation in WAAPI way
57 | const animate = useAnimation(
58 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
59 | {
60 | duration: 1000,
61 | easing: "ease-in-out",
62 | }
63 | );
64 |
65 | return (
66 | {
70 | // 3. And play it!
71 | animate.play();
72 | }}
73 | >
74 | Click Me!
75 |
76 | );
77 | };
78 | ```
79 |
80 | ### Dynamic keyframe
81 |
82 | Use `prev` and `args` for dynamic keyframe generation.
83 |
84 | ```tsx
85 | import { useEffect } from "react";
86 | import { useAnimation } from "react-animatable";
87 |
88 | export const App = () => {
89 | // Define argument type
90 | const animate = useAnimation<{ x: number; y: number }>(
91 | (prev, args) => [
92 | // You can get current style from 1st argument
93 | { transform: prev.transform },
94 | // Get passed position from 2nd argument
95 | { transform: `translate(${args.x}px, ${args.y}px)` },
96 | ],
97 | {
98 | duration: 400,
99 | easing: "ease-in-out",
100 | }
101 | );
102 |
103 | useEffect(() => {
104 | // If you click somewhere, the circle follows you!
105 |
106 | const onClick = (e: MouseEvent) => {
107 | // Pass mouse position when animate
108 | animate.play({ args: { x: e.clientX, y: e.clientY } });
109 | };
110 | window.addEventListener("click", onClick);
111 | return () => {
112 | window.removeEventListener("click", onClick);
113 | };
114 | }, []);
115 |
116 | return (
117 |
129 | );
130 | };
131 | ```
132 |
133 | ### Animation without CSS
134 |
135 | Use `useAnimationFunction` for JS only animation.
136 |
137 | ```tsx
138 | import { useState } from "react";
139 | import { useAnimationFunction } from "react-animatable";
140 |
141 | export const App = () => {
142 | const [value, setValue] = useState(0);
143 | const animate = useAnimationFunction(
144 | ({ progress }, arg) => {
145 | // Do anything here!
146 | setValue(progress * arg);
147 | },
148 | {
149 | duration: 600,
150 | easing: "ease-in-out",
151 | }
152 | );
153 | useEffect(() => {
154 | animate.play({ args: 100 });
155 | }, []);
156 |
157 | return ;
158 | };
159 | ```
160 |
161 | And see [examples](./stories) for more usages.
162 |
163 | ## Documentation
164 |
165 | - [API reference](./docs/API.md)
166 |
167 | ## Use polyfill
168 |
169 | 1. [browsers that have KeyframeEffect](https://caniuse.com/mdn-api_keyframeeffect)
170 | 1. [browsers that have Element.animate()](https://caniuse.com/mdn-api_element_animate)
171 | 1. browsers that have no Web Animations APIs
172 |
173 | In 1, you can use all functions of this library without polyfill. Some of the newer features like [composite mode](https://caniuse.com/web-animation) and [CSS Motion Path](https://caniuse.com/css-motion-paths) may be ignored in some browsers though.
174 |
175 | In 2, you can use this library but `useAnimationFuction` would not work.
176 |
177 | In 3, you have to setup [Web Animations API polyfill](https://github.com/web-animations/web-animations-js) to use this library.
178 |
179 | ### Setup web-animations-js
180 |
181 | ```sh
182 | npm install web-animations-js
183 | ```
184 |
185 | ```js
186 | // You can polyfill always
187 | import "web-animations-js";
188 | ReactDOM.render( );
189 |
190 | // or polyfill only if browser does not support Web Animations API
191 | (async () => {
192 | if (!("animate" in document.body)) {
193 | await import("web-animations-js");
194 | }
195 | ReactDOM.render( );
196 | })();
197 | ```
198 |
199 | #### `Partial keyframes are not supported` error was thrown
200 |
201 | web-animations-js does not support partial keyframes, so you have to write animation definitions like below.
202 |
203 | https://github.com/PolymerElements/paper-ripple/issues/28#issuecomment-266945027
204 |
205 | ```jsx
206 | // valid
207 | const animate = useAnimation(
208 | [
209 | { transform: "translate3d(0px, 0, 0)" },
210 | { transform: "translate3d(400px, 0, 0)" },
211 | ],
212 | { duration: 800, easing: "ease-in-out" }
213 | );
214 | // invalid
215 | const animate = useAnimation(
216 | { transform: "translate3d(400px, 0, 0)" },
217 | { duration: 800, easing: "ease-in-out" }
218 | );
219 |
220 | // valid
221 | const animate = useAnimation(
222 | [
223 | { transform: "translateX(0px)", fill: "blue" },
224 | { transform: "translateX(100px)", fill: "red" },
225 | { transform: "translateX(0px)", fill: "blue" },
226 | ],
227 | { duration: 800, easing: "ease-in-out" }
228 | );
229 | // invalid
230 | const animate = useAnimation(
231 | [
232 | { transform: "translateX(0px)" },
233 | { transform: "translateX(100px)", fill: "red" },
234 | { fill: "blue" },
235 | ],
236 | { duration: 800, easing: "ease-in-out" }
237 | );
238 | ```
239 |
240 | ## Contribute
241 |
242 | All contributions are welcome.
243 | If you find a problem, feel free to create an [issue](https://github.com/inokawa/react-animatable/issues) or a [PR](https://github.com/inokawa/react-animatable/pulls).
244 |
245 | ### Making a Pull Request
246 |
247 | 1. Fork this repo.
248 | 2. Run `npm install`.
249 | 3. Commit your fix.
250 | 4. Make a PR and confirm all the CI checks passed.
251 |
252 | ## My previous experiments (deprecated)
253 |
254 | - [tweened](https://github.com/inokawa/tweened)
255 | - [react-use-d3](https://github.com/inokawa/react-use-d3)
256 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inokawa/react-animatable/67622b5b57c1721c8aaed8317e9d4a1e7fe1b459/demo.gif
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | ## Table of contents
4 |
5 | ### Functions
6 |
7 | - [TransitionGroup](API.md#transitiongroup)
8 | - [useAnimation](API.md#useanimation)
9 | - [useAnimationFunction](API.md#useanimationfunction)
10 | - [useTransitionAnimation](API.md#usetransitionanimation)
11 | - [useScrollTimeline](API.md#usescrolltimeline)
12 | - [useViewTimeline](API.md#useviewtimeline)
13 |
14 | ### Interfaces
15 |
16 | - [TypedKeyframeEffectOptions](interfaces/TypedKeyframeEffectOptions.md)
17 | - [TransitionGroupProps](interfaces/TransitionGroupProps.md)
18 | - [BaseAnimationHandle](interfaces/BaseAnimationHandle.md)
19 | - [AnimationHandle](interfaces/AnimationHandle.md)
20 | - [AnimationFunctionHandle](interfaces/AnimationFunctionHandle.md)
21 | - [AnimationFunctionOptions](interfaces/AnimationFunctionOptions.md)
22 | - [TransitionAnimationHandle](interfaces/TransitionAnimationHandle.md)
23 | - [TransitionAnimationOptions](interfaces/TransitionAnimationOptions.md)
24 | - [ScrollTimelineOpts](interfaces/ScrollTimelineOpts.md)
25 | - [ViewTimelineOpts](interfaces/ViewTimelineOpts.md)
26 | - [AnimationOptions](interfaces/AnimationOptions.md)
27 | - [TimelineDefinition](interfaces/TimelineDefinition.md)
28 |
29 | ### Type Aliases
30 |
31 | - [TypedKeyframe](API.md#typedkeyframe)
32 | - [TypedEasing](API.md#typedeasing)
33 | - [GetKeyframeFunction](API.md#getkeyframefunction)
34 | - [AnimatableCSSProperties](API.md#animatablecssproperties)
35 | - [PlayOptions](API.md#playoptions)
36 | - [WaitingAnimationEventName](API.md#waitinganimationeventname)
37 | - [PlayOptionsWithArgs](API.md#playoptionswithargs)
38 | - [ComputedTimingContext](API.md#computedtimingcontext)
39 | - [AnimationFunction](API.md#animationfunction)
40 | - [TransitionAnimationDefinition](API.md#transitionanimationdefinition)
41 | - [AnimationDefinition](API.md#animationdefinition)
42 | - [ScrollTimelineAxis](API.md#scrolltimelineaxis)
43 | - [ViewTimelineInset](API.md#viewtimelineinset)
44 |
45 | ## Functions
46 |
47 | ### TransitionGroup
48 |
49 | ▸ **TransitionGroup**(`«destructured»`): `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\>
50 |
51 | A component to manage enter/update/exit of its children by key, that works similar to [TransitionGroup of react-transition-group](https://reactcommunity.org/react-transition-group/transition-group).
52 |
53 | #### Parameters
54 |
55 | | Name | Type |
56 | | :------ | :------ |
57 | | `«destructured»` | [`TransitionGroupProps`](interfaces/TransitionGroupProps.md) |
58 |
59 | #### Returns
60 |
61 | `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\>
62 |
63 | #### Defined in
64 |
65 | [src/react/components/TransitionGroup.tsx:81](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/components/TransitionGroup.tsx#L81)
66 |
67 | ___
68 |
69 | ### useAnimation
70 |
71 | ▸ **useAnimation**<`Args`\>(`...args`): [`AnimationHandle`](interfaces/AnimationHandle.md)<`Args`\>
72 |
73 | A basic hook to use Web Animations API. See [AnimationHandle](interfaces/AnimationHandle.md).
74 |
75 | #### Type parameters
76 |
77 | | Name | Type | Description |
78 | | :------ | :------ | :------ |
79 | | `Args` | `void` | argument type |
80 |
81 | #### Parameters
82 |
83 | | Name | Type |
84 | | :------ | :------ |
85 | | `...args` | [`AnimationDefinition`](API.md#animationdefinition)<`Args`\> |
86 |
87 | #### Returns
88 |
89 | [`AnimationHandle`](interfaces/AnimationHandle.md)<`Args`\>
90 |
91 | #### Defined in
92 |
93 | [src/react/hooks/useAnimation.ts:104](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L104)
94 |
95 | ___
96 |
97 | ### useAnimationFunction
98 |
99 | ▸ **useAnimationFunction**<`Args`\>(`onUpdate`, `options?`): [`AnimationFunctionHandle`](interfaces/AnimationFunctionHandle.md)<`Args`\>
100 |
101 | Same as [useAnimation](API.md#useanimation), but it drives function not React element. See [AnimationFunctionHandle](interfaces/AnimationFunctionHandle.md).
102 |
103 | #### Type parameters
104 |
105 | | Name | Type | Description |
106 | | :------ | :------ | :------ |
107 | | `Args` | `void` | argument type |
108 |
109 | #### Parameters
110 |
111 | | Name | Type |
112 | | :------ | :------ |
113 | | `onUpdate` | [`AnimationFunction`](API.md#animationfunction)<`Args`\> |
114 | | `options?` | [`AnimationFunctionOptions`](interfaces/AnimationFunctionOptions.md) |
115 |
116 | #### Returns
117 |
118 | [`AnimationFunctionHandle`](interfaces/AnimationFunctionHandle.md)<`Args`\>
119 |
120 | #### Defined in
121 |
122 | [src/react/hooks/useAnimationFunction.ts:69](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimationFunction.ts#L69)
123 |
124 | ___
125 |
126 | ### useTransitionAnimation
127 |
128 | ▸ **useTransitionAnimation**(`keyframes`): [`TransitionAnimationHandle`](interfaces/TransitionAnimationHandle.md)
129 |
130 | A hook to compose multiple [useAnimation](API.md#useanimation) and plays them when element enter/update/exits.
131 | This hook must be used under [TransitionGroup](API.md#transitiongroup) component.
132 |
133 | #### Parameters
134 |
135 | | Name | Type |
136 | | :------ | :------ |
137 | | `keyframes` | `Object` |
138 | | `keyframes.enter?` | [`TransitionAnimationDefinition`](API.md#transitionanimationdefinition) |
139 | | `keyframes.update?` | [`TransitionAnimationDefinition`](API.md#transitionanimationdefinition) |
140 | | `keyframes.exit?` | [`TransitionAnimationDefinition`](API.md#transitionanimationdefinition) |
141 |
142 | #### Returns
143 |
144 | [`TransitionAnimationHandle`](interfaces/TransitionAnimationHandle.md)
145 |
146 | #### Defined in
147 |
148 | [src/react/hooks/useTransitionAnimation.ts:38](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useTransitionAnimation.ts#L38)
149 |
150 | ___
151 |
152 | ### useScrollTimeline
153 |
154 | ▸ **useScrollTimeline**(`opts?`): [`TimelineDefinition`](interfaces/TimelineDefinition.md)
155 |
156 | #### Parameters
157 |
158 | | Name | Type |
159 | | :------ | :------ |
160 | | `opts` | [`ScrollTimelineOpts`](interfaces/ScrollTimelineOpts.md) |
161 |
162 | #### Returns
163 |
164 | [`TimelineDefinition`](interfaces/TimelineDefinition.md)
165 |
166 | #### Defined in
167 |
168 | [src/react/hooks/useScrollTimeline.ts:11](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useScrollTimeline.ts#L11)
169 |
170 | ___
171 |
172 | ### useViewTimeline
173 |
174 | ▸ **useViewTimeline**(`opts?`): [`TimelineDefinition`](interfaces/TimelineDefinition.md)
175 |
176 | #### Parameters
177 |
178 | | Name | Type |
179 | | :------ | :------ |
180 | | `opts` | [`ViewTimelineOpts`](interfaces/ViewTimelineOpts.md) |
181 |
182 | #### Returns
183 |
184 | [`TimelineDefinition`](interfaces/TimelineDefinition.md)
185 |
186 | #### Defined in
187 |
188 | [src/react/hooks/useViewTimeline.ts:16](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useViewTimeline.ts#L16)
189 |
190 | ## Type Aliases
191 |
192 | ### TypedKeyframe
193 |
194 | Ƭ **TypedKeyframe**: `Pick`<`Keyframe`, ``"composite"`` \| ``"easing"`` \| ``"offset"``\> & { `[key: `--${string}`]`: `string` \| `number`; } & [`AnimatableCSSProperties`](API.md#animatablecssproperties)
195 |
196 | Strictly typed [Keyframe](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats)
197 |
198 | #### Defined in
199 |
200 | [src/core/waapi.ts:17](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L17)
201 |
202 | ___
203 |
204 | ### TypedEasing
205 |
206 | Ƭ **TypedEasing**: `Exclude`<`CSSProperties`[``"animationTimingFunction"``], `CSSProperties`[``"all"``] \| `undefined`\>
207 |
208 | #### Defined in
209 |
210 | [src/core/waapi.ts:22](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L22)
211 |
212 | ___
213 |
214 | ### GetKeyframeFunction
215 |
216 | Ƭ **GetKeyframeFunction**<`Args`\>: `Args` extends `void` ? (`prev`: `CSSStyleDeclaration`) => [`TypedKeyframe`](API.md#typedkeyframe)[] : (`prev`: `CSSStyleDeclaration`, `args`: `Args`) => [`TypedKeyframe`](API.md#typedkeyframe)[]
217 |
218 | A function to define keyframe dynamically
219 | - `prev`: current style
220 | - `args`: any argument passed from play
221 |
222 | #### Type parameters
223 |
224 | | Name | Type |
225 | | :------ | :------ |
226 | | `Args` | `void` |
227 |
228 | #### Defined in
229 |
230 | [src/core/waapi.ts:32](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L32)
231 |
232 | ___
233 |
234 | ### AnimatableCSSProperties
235 |
236 | Ƭ **AnimatableCSSProperties**: `Omit`<`CSSProperties`, ``"offset"`` \| ``"float"``\> & { `cssOffset?`: `CSSProperties`[``"offset"``] ; `cssFloat?`: `CSSProperties`[``"float"``] ; `d?`: `string` }
237 |
238 | #### Defined in
239 |
240 | [src/core/waapi.ts:4](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L4)
241 |
242 | ___
243 |
244 | ### PlayOptions
245 |
246 | Ƭ **PlayOptions**: `Object`
247 |
248 | #### Type declaration
249 |
250 | | Name | Type | Description |
251 | | :------ | :------ | :------ |
252 | | `restart?` | `boolean` | If true, plays from the start. It's similar to GSAP's `restart()`. |
253 |
254 | #### Defined in
255 |
256 | [src/core/waapi.ts:85](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L85)
257 |
258 | ___
259 |
260 | ### WaitingAnimationEventName
261 |
262 | Ƭ **WaitingAnimationEventName**: ``"finish"`` \| ``"reverseFinish"``
263 |
264 | #### Defined in
265 |
266 | [src/core/waapi.ts:185](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L185)
267 |
268 | ___
269 |
270 | ### PlayOptionsWithArgs
271 |
272 | Ƭ **PlayOptionsWithArgs**<`Args`\>: [`PlayOptions`](API.md#playoptions) & { `args`: `Args` }
273 |
274 | #### Type parameters
275 |
276 | | Name | Type |
277 | | :------ | :------ |
278 | | `Args` | `void` |
279 |
280 | #### Defined in
281 |
282 | [src/react/hooks/useAnimation.ts:38](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L38)
283 |
284 | ___
285 |
286 | ### ComputedTimingContext
287 |
288 | Ƭ **ComputedTimingContext**: `Required`<{ [key in keyof ComputedEffectTiming]: NonNullable }\>
289 |
290 | Non nullable [ComputedEffectTiming](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming)
291 |
292 | #### Defined in
293 |
294 | [src/react/hooks/useAnimationFunction.ts:32](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimationFunction.ts#L32)
295 |
296 | ___
297 |
298 | ### AnimationFunction
299 |
300 | Ƭ **AnimationFunction**<`Args`\>: `Args` extends `void` ? (`ctx`: [`ComputedTimingContext`](API.md#computedtimingcontext)) => `void` : (`ctx`: [`ComputedTimingContext`](API.md#computedtimingcontext), `args`: `Args`) => `void`
301 |
302 | An argument of [useAnimationFunction](API.md#useanimationfunction).
303 | In this callback you can update any state or ref in JS.
304 | - `ctx`: current animation state
305 | - `args`: any argument passed from play
306 |
307 | #### Type parameters
308 |
309 | | Name | Type |
310 | | :------ | :------ |
311 | | `Args` | `void` |
312 |
313 | #### Defined in
314 |
315 | [src/react/hooks/useAnimationFunction.ts:42](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimationFunction.ts#L42)
316 |
317 | ___
318 |
319 | ### TransitionAnimationDefinition
320 |
321 | Ƭ **TransitionAnimationDefinition**: [keyframe: TypedKeyframe \| TypedKeyframe[] \| GetKeyframeFunction, options?: TransitionAnimationOptions]
322 |
323 | #### Defined in
324 |
325 | [src/react/hooks/useTransitionAnimation.ts:28](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useTransitionAnimation.ts#L28)
326 |
327 | ___
328 |
329 | ### AnimationDefinition
330 |
331 | Ƭ **AnimationDefinition**<`Args`\>: [keyframe: TypedKeyframe \| TypedKeyframe[] \| GetKeyframeFunction, options?: AnimationOptions]
332 |
333 | #### Type parameters
334 |
335 | | Name |
336 | | :------ |
337 | | `Args` |
338 |
339 | #### Defined in
340 |
341 | [src/react/types/index.ts:12](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/types/index.ts#L12)
342 |
343 | ___
344 |
345 | ### ScrollTimelineAxis
346 |
347 | Ƭ **ScrollTimelineAxis**: ``"block"`` \| ``"inline"`` \| ``"y"`` \| ``"x"``
348 |
349 | #### Defined in
350 |
351 | [src/react/types/index.ts:29](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/types/index.ts#L29)
352 |
353 | ___
354 |
355 | ### ViewTimelineInset
356 |
357 | Ƭ **ViewTimelineInset**: ``"auto"`` \| `string` & {}
358 |
359 | #### Defined in
360 |
361 | [src/react/types/index.ts:31](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/types/index.ts#L31)
362 |
--------------------------------------------------------------------------------
/docs/interfaces/AnimationFunctionHandle.md:
--------------------------------------------------------------------------------
1 | # Interface: AnimationFunctionHandle
2 |
3 | Handle of [useAnimationFunction](../API.md#useanimationfunction).
4 |
5 | ## Type parameters
6 |
7 | | Name | Type | Description |
8 | | :------ | :------ | :------ |
9 | | `Args` | `void` | argument type |
10 |
11 | ## Hierarchy
12 |
13 | - [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
14 |
15 | ↳ **`AnimationFunctionHandle`**
16 |
17 | ## Table of contents
18 |
19 | ### Methods
20 |
21 | - [play](AnimationFunctionHandle.md#play)
22 | - [reverse](AnimationFunctionHandle.md#reverse)
23 | - [cancel](AnimationFunctionHandle.md#cancel)
24 | - [finish](AnimationFunctionHandle.md#finish)
25 | - [pause](AnimationFunctionHandle.md#pause)
26 | - [setTime](AnimationFunctionHandle.md#settime)
27 | - [setPlaybackRate](AnimationFunctionHandle.md#setplaybackrate)
28 | - [waitFor](AnimationFunctionHandle.md#waitfor)
29 |
30 | ## Methods
31 |
32 | ### play
33 |
34 | ▸ **play**(`...opts`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
35 |
36 | A wrapper of Web Animations API's [play](https://developer.mozilla.org/en-US/docs/Web/API/Animation/play). It's similar to GSAP's `play()`.
37 |
38 | #### Parameters
39 |
40 | | Name | Type |
41 | | :------ | :------ |
42 | | `...opts` | `Args` extends `void` ? [PlayOptions?] : [[`PlayOptionsWithArgs`](../API.md#playoptionswithargs)<`Args`\>] |
43 |
44 | #### Returns
45 |
46 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
47 |
48 | #### Inherited from
49 |
50 | [BaseAnimationHandle](BaseAnimationHandle.md).[play](BaseAnimationHandle.md#play)
51 |
52 | #### Defined in
53 |
54 | [src/react/hooks/useAnimation.ts:44](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L44)
55 |
56 | ___
57 |
58 | ### reverse
59 |
60 | ▸ **reverse**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
61 |
62 | A wrapper of Web Animations API's [reverse](https://developer.mozilla.org/en-US/docs/Web/API/Animation/reverse). It's similar to GSAP's `reverse()`.
63 |
64 | #### Returns
65 |
66 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
67 |
68 | #### Inherited from
69 |
70 | [BaseAnimationHandle](BaseAnimationHandle.md).[reverse](BaseAnimationHandle.md#reverse)
71 |
72 | #### Defined in
73 |
74 | [src/react/hooks/useAnimation.ts:50](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L50)
75 |
76 | ___
77 |
78 | ### cancel
79 |
80 | ▸ **cancel**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
81 |
82 | A wrapper of Web Animations API's [cancel](https://developer.mozilla.org/en-US/docs/Web/API/Animation/cancel). It's similar to GSAP's `kill()`.
83 |
84 | #### Returns
85 |
86 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
87 |
88 | #### Inherited from
89 |
90 | [BaseAnimationHandle](BaseAnimationHandle.md).[cancel](BaseAnimationHandle.md#cancel)
91 |
92 | #### Defined in
93 |
94 | [src/react/hooks/useAnimation.ts:54](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L54)
95 |
96 | ___
97 |
98 | ### finish
99 |
100 | ▸ **finish**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
101 |
102 | A wrapper of Web Animations API's [finish](https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish).
103 |
104 | #### Returns
105 |
106 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
107 |
108 | #### Inherited from
109 |
110 | [BaseAnimationHandle](BaseAnimationHandle.md).[finish](BaseAnimationHandle.md#finish)
111 |
112 | #### Defined in
113 |
114 | [src/react/hooks/useAnimation.ts:58](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L58)
115 |
116 | ___
117 |
118 | ### pause
119 |
120 | ▸ **pause**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
121 |
122 | A wrapper of Web Animations API's [pause](https://developer.mozilla.org/en-US/docs/Web/API/Animation/pause). It's similar to GSAP's `pause()`.
123 |
124 | #### Returns
125 |
126 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
127 |
128 | #### Inherited from
129 |
130 | [BaseAnimationHandle](BaseAnimationHandle.md).[pause](BaseAnimationHandle.md#pause)
131 |
132 | #### Defined in
133 |
134 | [src/react/hooks/useAnimation.ts:62](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L62)
135 |
136 | ___
137 |
138 | ### setTime
139 |
140 | ▸ **setTime**(`time`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
141 |
142 | A setter of Web Animations API's [currentTime](https://developer.mozilla.org/en-US/docs/Web/API/Animation/currentTime). It's similar to GSAP's `seek()`.
143 |
144 | If you pass function, you can get [endTime](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming#return_value) from its argument.
145 |
146 | #### Parameters
147 |
148 | | Name | Type |
149 | | :------ | :------ |
150 | | `time` | `number` \| (`endTime`: `number`) => `number` |
151 |
152 | #### Returns
153 |
154 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
155 |
156 | #### Inherited from
157 |
158 | [BaseAnimationHandle](BaseAnimationHandle.md).[setTime](BaseAnimationHandle.md#settime)
159 |
160 | #### Defined in
161 |
162 | [src/react/hooks/useAnimation.ts:68](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L68)
163 |
164 | ___
165 |
166 | ### setPlaybackRate
167 |
168 | ▸ **setPlaybackRate**(`rate`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
169 |
170 | A wrapper of Web Animations API's [updatePlaybackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/updatePlaybackRate). It's similar to GSAP's `timeScale()`.
171 |
172 | If you pass function, you can get current [playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate) from its argument.
173 |
174 | #### Parameters
175 |
176 | | Name | Type |
177 | | :------ | :------ |
178 | | `rate` | `number` \| (`prevRate`: `number`) => `number` |
179 |
180 | #### Returns
181 |
182 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
183 |
184 | #### Inherited from
185 |
186 | [BaseAnimationHandle](BaseAnimationHandle.md).[setPlaybackRate](BaseAnimationHandle.md#setplaybackrate)
187 |
188 | #### Defined in
189 |
190 | [src/react/hooks/useAnimation.ts:76](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L76)
191 |
192 | ___
193 |
194 | ### waitFor
195 |
196 | ▸ **waitFor**(`event`): `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
197 |
198 | A getter of Promise that will be resolved in specified timing.
199 |
200 | - `finished`: resolved when animation is finished and its playback direction is normal.
201 | - `reverseFinished`: resolved when animation is finished and its playback direction is reversed.
202 |
203 | #### Parameters
204 |
205 | | Name | Type |
206 | | :------ | :------ |
207 | | `event` | [`WaitingAnimationEventName`](../API.md#waitinganimationeventname) |
208 |
209 | #### Returns
210 |
211 | `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
212 |
213 | #### Inherited from
214 |
215 | [BaseAnimationHandle](BaseAnimationHandle.md).[waitFor](BaseAnimationHandle.md#waitfor)
216 |
217 | #### Defined in
218 |
219 | [src/react/hooks/useAnimation.ts:85](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L85)
220 |
--------------------------------------------------------------------------------
/docs/interfaces/AnimationFunctionOptions.md:
--------------------------------------------------------------------------------
1 | # Interface: AnimationFunctionOptions
2 |
3 | Extended [options of KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect)
4 |
5 | ## Hierarchy
6 |
7 | - [`TypedKeyframeEffectOptions`](TypedKeyframeEffectOptions.md)
8 |
9 | ↳ **`AnimationFunctionOptions`**
10 |
11 | ## Table of contents
12 |
13 | ### Properties
14 |
15 | - [easing](AnimationFunctionOptions.md#easing)
16 | - [fill](AnimationFunctionOptions.md#fill)
17 | - [direction](AnimationFunctionOptions.md#direction)
18 | - [composite](AnimationFunctionOptions.md#composite)
19 | - [iterationComposite](AnimationFunctionOptions.md#iterationcomposite)
20 | - [pseudoElement](AnimationFunctionOptions.md#pseudoelement)
21 | - [delay](AnimationFunctionOptions.md#delay)
22 | - [duration](AnimationFunctionOptions.md#duration)
23 | - [endDelay](AnimationFunctionOptions.md#enddelay)
24 | - [iterationStart](AnimationFunctionOptions.md#iterationstart)
25 | - [iterations](AnimationFunctionOptions.md#iterations)
26 | - [playbackRate](AnimationFunctionOptions.md#playbackrate)
27 |
28 | ## Properties
29 |
30 | ### easing
31 |
32 | • `Optional` **easing**: [`TypedEasing`](../API.md#typedeasing)
33 |
34 | #### Inherited from
35 |
36 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[easing](TypedKeyframeEffectOptions.md#easing)
37 |
38 | #### Defined in
39 |
40 | [src/core/waapi.ts:41](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L41)
41 |
42 | ___
43 |
44 | ### fill
45 |
46 | • `Optional` **fill**: `FillMode`
47 |
48 | #### Inherited from
49 |
50 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[fill](TypedKeyframeEffectOptions.md#fill)
51 |
52 | #### Defined in
53 |
54 | node_modules/typescript/lib/lib.dom.d.ts:449
55 |
56 | ___
57 |
58 | ### direction
59 |
60 | • `Optional` **direction**: `PlaybackDirection`
61 |
62 | #### Inherited from
63 |
64 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[direction](TypedKeyframeEffectOptions.md#direction)
65 |
66 | #### Defined in
67 |
68 | node_modules/typescript/lib/lib.dom.d.ts:445
69 |
70 | ___
71 |
72 | ### composite
73 |
74 | • `Optional` **composite**: `CompositeOperation`
75 |
76 | #### Inherited from
77 |
78 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[composite](TypedKeyframeEffectOptions.md#composite)
79 |
80 | #### Defined in
81 |
82 | node_modules/typescript/lib/lib.dom.d.ts:757
83 |
84 | ___
85 |
86 | ### iterationComposite
87 |
88 | • `Optional` **iterationComposite**: `IterationCompositeOperation`
89 |
90 | #### Inherited from
91 |
92 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationComposite](TypedKeyframeEffectOptions.md#iterationcomposite)
93 |
94 | #### Defined in
95 |
96 | node_modules/typescript/lib/lib.dom.d.ts:758
97 |
98 | ___
99 |
100 | ### pseudoElement
101 |
102 | • `Optional` **pseudoElement**: ``null`` \| `string`
103 |
104 | #### Inherited from
105 |
106 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[pseudoElement](TypedKeyframeEffectOptions.md#pseudoelement)
107 |
108 | #### Defined in
109 |
110 | node_modules/typescript/lib/lib.dom.d.ts:759
111 |
112 | ___
113 |
114 | ### delay
115 |
116 | • `Optional` **delay**: `number`
117 |
118 | #### Inherited from
119 |
120 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[delay](TypedKeyframeEffectOptions.md#delay)
121 |
122 | #### Defined in
123 |
124 | node_modules/typescript/lib/lib.dom.d.ts:444
125 |
126 | ___
127 |
128 | ### duration
129 |
130 | • `Optional` **duration**: `string` \| `number` \| `CSSNumericValue`
131 |
132 | #### Inherited from
133 |
134 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[duration](TypedKeyframeEffectOptions.md#duration)
135 |
136 | #### Defined in
137 |
138 | node_modules/typescript/lib/lib.dom.d.ts:446
139 |
140 | ___
141 |
142 | ### endDelay
143 |
144 | • `Optional` **endDelay**: `number`
145 |
146 | #### Inherited from
147 |
148 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[endDelay](TypedKeyframeEffectOptions.md#enddelay)
149 |
150 | #### Defined in
151 |
152 | node_modules/typescript/lib/lib.dom.d.ts:448
153 |
154 | ___
155 |
156 | ### iterationStart
157 |
158 | • `Optional` **iterationStart**: `number`
159 |
160 | #### Inherited from
161 |
162 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationStart](TypedKeyframeEffectOptions.md#iterationstart)
163 |
164 | #### Defined in
165 |
166 | node_modules/typescript/lib/lib.dom.d.ts:450
167 |
168 | ___
169 |
170 | ### iterations
171 |
172 | • `Optional` **iterations**: `number`
173 |
174 | #### Inherited from
175 |
176 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterations](TypedKeyframeEffectOptions.md#iterations)
177 |
178 | #### Defined in
179 |
180 | node_modules/typescript/lib/lib.dom.d.ts:451
181 |
182 | ___
183 |
184 | ### playbackRate
185 |
186 | • `Optional` **playbackRate**: `number`
187 |
188 | #### Inherited from
189 |
190 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[playbackRate](TypedKeyframeEffectOptions.md#playbackrate)
191 |
192 | #### Defined in
193 |
194 | node_modules/typescript/lib/lib.dom.d.ts:452
195 |
--------------------------------------------------------------------------------
/docs/interfaces/AnimationHandle.md:
--------------------------------------------------------------------------------
1 | # Interface: AnimationHandle
2 |
3 | Handle of [useAnimation](../API.md#useanimation).
4 |
5 | ## Type parameters
6 |
7 | | Name | Type | Description |
8 | | :------ | :------ | :------ |
9 | | `Args` | `void` | argument type |
10 |
11 | ## Hierarchy
12 |
13 | - [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
14 |
15 | ↳ **`AnimationHandle`**
16 |
17 | ## Callable
18 |
19 | ### AnimationHandle
20 |
21 | ▸ **AnimationHandle**(`ref`): `void`
22 |
23 | You have to pass this callback to ref of element you want to control.
24 |
25 | #### Parameters
26 |
27 | | Name | Type |
28 | | :------ | :------ |
29 | | `ref` | ``null`` \| `Element` |
30 |
31 | #### Returns
32 |
33 | `void`
34 |
35 | #### Defined in
36 |
37 | [src/react/hooks/useAnimation.ts:97](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L97)
38 |
39 | ## Table of contents
40 |
41 | ### Methods
42 |
43 | - [play](AnimationHandle.md#play)
44 | - [reverse](AnimationHandle.md#reverse)
45 | - [cancel](AnimationHandle.md#cancel)
46 | - [finish](AnimationHandle.md#finish)
47 | - [pause](AnimationHandle.md#pause)
48 | - [setTime](AnimationHandle.md#settime)
49 | - [setPlaybackRate](AnimationHandle.md#setplaybackrate)
50 | - [waitFor](AnimationHandle.md#waitfor)
51 |
52 | ## Methods
53 |
54 | ### play
55 |
56 | ▸ **play**(`...opts`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
57 |
58 | A wrapper of Web Animations API's [play](https://developer.mozilla.org/en-US/docs/Web/API/Animation/play). It's similar to GSAP's `play()`.
59 |
60 | #### Parameters
61 |
62 | | Name | Type |
63 | | :------ | :------ |
64 | | `...opts` | `Args` extends `void` ? [PlayOptions?] : [[`PlayOptionsWithArgs`](../API.md#playoptionswithargs)<`Args`\>] |
65 |
66 | #### Returns
67 |
68 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
69 |
70 | #### Inherited from
71 |
72 | [BaseAnimationHandle](BaseAnimationHandle.md).[play](BaseAnimationHandle.md#play)
73 |
74 | #### Defined in
75 |
76 | [src/react/hooks/useAnimation.ts:44](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L44)
77 |
78 | ___
79 |
80 | ### reverse
81 |
82 | ▸ **reverse**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
83 |
84 | A wrapper of Web Animations API's [reverse](https://developer.mozilla.org/en-US/docs/Web/API/Animation/reverse). It's similar to GSAP's `reverse()`.
85 |
86 | #### Returns
87 |
88 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
89 |
90 | #### Inherited from
91 |
92 | [BaseAnimationHandle](BaseAnimationHandle.md).[reverse](BaseAnimationHandle.md#reverse)
93 |
94 | #### Defined in
95 |
96 | [src/react/hooks/useAnimation.ts:50](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L50)
97 |
98 | ___
99 |
100 | ### cancel
101 |
102 | ▸ **cancel**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
103 |
104 | A wrapper of Web Animations API's [cancel](https://developer.mozilla.org/en-US/docs/Web/API/Animation/cancel). It's similar to GSAP's `kill()`.
105 |
106 | #### Returns
107 |
108 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
109 |
110 | #### Inherited from
111 |
112 | [BaseAnimationHandle](BaseAnimationHandle.md).[cancel](BaseAnimationHandle.md#cancel)
113 |
114 | #### Defined in
115 |
116 | [src/react/hooks/useAnimation.ts:54](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L54)
117 |
118 | ___
119 |
120 | ### finish
121 |
122 | ▸ **finish**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
123 |
124 | A wrapper of Web Animations API's [finish](https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish).
125 |
126 | #### Returns
127 |
128 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
129 |
130 | #### Inherited from
131 |
132 | [BaseAnimationHandle](BaseAnimationHandle.md).[finish](BaseAnimationHandle.md#finish)
133 |
134 | #### Defined in
135 |
136 | [src/react/hooks/useAnimation.ts:58](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L58)
137 |
138 | ___
139 |
140 | ### pause
141 |
142 | ▸ **pause**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
143 |
144 | A wrapper of Web Animations API's [pause](https://developer.mozilla.org/en-US/docs/Web/API/Animation/pause). It's similar to GSAP's `pause()`.
145 |
146 | #### Returns
147 |
148 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
149 |
150 | #### Inherited from
151 |
152 | [BaseAnimationHandle](BaseAnimationHandle.md).[pause](BaseAnimationHandle.md#pause)
153 |
154 | #### Defined in
155 |
156 | [src/react/hooks/useAnimation.ts:62](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L62)
157 |
158 | ___
159 |
160 | ### setTime
161 |
162 | ▸ **setTime**(`time`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
163 |
164 | A setter of Web Animations API's [currentTime](https://developer.mozilla.org/en-US/docs/Web/API/Animation/currentTime). It's similar to GSAP's `seek()`.
165 |
166 | If you pass function, you can get [endTime](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming#return_value) from its argument.
167 |
168 | #### Parameters
169 |
170 | | Name | Type |
171 | | :------ | :------ |
172 | | `time` | `number` \| (`endTime`: `number`) => `number` |
173 |
174 | #### Returns
175 |
176 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
177 |
178 | #### Inherited from
179 |
180 | [BaseAnimationHandle](BaseAnimationHandle.md).[setTime](BaseAnimationHandle.md#settime)
181 |
182 | #### Defined in
183 |
184 | [src/react/hooks/useAnimation.ts:68](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L68)
185 |
186 | ___
187 |
188 | ### setPlaybackRate
189 |
190 | ▸ **setPlaybackRate**(`rate`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
191 |
192 | A wrapper of Web Animations API's [updatePlaybackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/updatePlaybackRate). It's similar to GSAP's `timeScale()`.
193 |
194 | If you pass function, you can get current [playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate) from its argument.
195 |
196 | #### Parameters
197 |
198 | | Name | Type |
199 | | :------ | :------ |
200 | | `rate` | `number` \| (`prevRate`: `number`) => `number` |
201 |
202 | #### Returns
203 |
204 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
205 |
206 | #### Inherited from
207 |
208 | [BaseAnimationHandle](BaseAnimationHandle.md).[setPlaybackRate](BaseAnimationHandle.md#setplaybackrate)
209 |
210 | #### Defined in
211 |
212 | [src/react/hooks/useAnimation.ts:76](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L76)
213 |
214 | ___
215 |
216 | ### waitFor
217 |
218 | ▸ **waitFor**(`event`): `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
219 |
220 | A getter of Promise that will be resolved in specified timing.
221 |
222 | - `finished`: resolved when animation is finished and its playback direction is normal.
223 | - `reverseFinished`: resolved when animation is finished and its playback direction is reversed.
224 |
225 | #### Parameters
226 |
227 | | Name | Type |
228 | | :------ | :------ |
229 | | `event` | [`WaitingAnimationEventName`](../API.md#waitinganimationeventname) |
230 |
231 | #### Returns
232 |
233 | `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
234 |
235 | #### Inherited from
236 |
237 | [BaseAnimationHandle](BaseAnimationHandle.md).[waitFor](BaseAnimationHandle.md#waitfor)
238 |
239 | #### Defined in
240 |
241 | [src/react/hooks/useAnimation.ts:85](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L85)
242 |
--------------------------------------------------------------------------------
/docs/interfaces/AnimationOptions.md:
--------------------------------------------------------------------------------
1 | # Interface: AnimationOptions
2 |
3 | Extended [options of KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect)
4 |
5 | ## Hierarchy
6 |
7 | - [`TypedKeyframeEffectOptions`](TypedKeyframeEffectOptions.md)
8 |
9 | ↳ **`AnimationOptions`**
10 |
11 | ## Table of contents
12 |
13 | ### Properties
14 |
15 | - [timeline](AnimationOptions.md#timeline)
16 | - [easing](AnimationOptions.md#easing)
17 | - [fill](AnimationOptions.md#fill)
18 | - [direction](AnimationOptions.md#direction)
19 | - [composite](AnimationOptions.md#composite)
20 | - [iterationComposite](AnimationOptions.md#iterationcomposite)
21 | - [pseudoElement](AnimationOptions.md#pseudoelement)
22 | - [delay](AnimationOptions.md#delay)
23 | - [duration](AnimationOptions.md#duration)
24 | - [endDelay](AnimationOptions.md#enddelay)
25 | - [iterationStart](AnimationOptions.md#iterationstart)
26 | - [iterations](AnimationOptions.md#iterations)
27 | - [playbackRate](AnimationOptions.md#playbackrate)
28 |
29 | ## Properties
30 |
31 | ### timeline
32 |
33 | • `Optional` **timeline**: [`TimelineDefinition`](TimelineDefinition.md)
34 |
35 | #### Defined in
36 |
37 | [src/react/types/index.ts:9](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/types/index.ts#L9)
38 |
39 | ___
40 |
41 | ### easing
42 |
43 | • `Optional` **easing**: [`TypedEasing`](../API.md#typedeasing)
44 |
45 | #### Inherited from
46 |
47 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[easing](TypedKeyframeEffectOptions.md#easing)
48 |
49 | #### Defined in
50 |
51 | [src/core/waapi.ts:41](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L41)
52 |
53 | ___
54 |
55 | ### fill
56 |
57 | • `Optional` **fill**: `FillMode`
58 |
59 | #### Inherited from
60 |
61 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[fill](TypedKeyframeEffectOptions.md#fill)
62 |
63 | #### Defined in
64 |
65 | node_modules/typescript/lib/lib.dom.d.ts:449
66 |
67 | ___
68 |
69 | ### direction
70 |
71 | • `Optional` **direction**: `PlaybackDirection`
72 |
73 | #### Inherited from
74 |
75 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[direction](TypedKeyframeEffectOptions.md#direction)
76 |
77 | #### Defined in
78 |
79 | node_modules/typescript/lib/lib.dom.d.ts:445
80 |
81 | ___
82 |
83 | ### composite
84 |
85 | • `Optional` **composite**: `CompositeOperation`
86 |
87 | #### Inherited from
88 |
89 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[composite](TypedKeyframeEffectOptions.md#composite)
90 |
91 | #### Defined in
92 |
93 | node_modules/typescript/lib/lib.dom.d.ts:757
94 |
95 | ___
96 |
97 | ### iterationComposite
98 |
99 | • `Optional` **iterationComposite**: `IterationCompositeOperation`
100 |
101 | #### Inherited from
102 |
103 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationComposite](TypedKeyframeEffectOptions.md#iterationcomposite)
104 |
105 | #### Defined in
106 |
107 | node_modules/typescript/lib/lib.dom.d.ts:758
108 |
109 | ___
110 |
111 | ### pseudoElement
112 |
113 | • `Optional` **pseudoElement**: ``null`` \| `string`
114 |
115 | #### Inherited from
116 |
117 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[pseudoElement](TypedKeyframeEffectOptions.md#pseudoelement)
118 |
119 | #### Defined in
120 |
121 | node_modules/typescript/lib/lib.dom.d.ts:759
122 |
123 | ___
124 |
125 | ### delay
126 |
127 | • `Optional` **delay**: `number`
128 |
129 | #### Inherited from
130 |
131 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[delay](TypedKeyframeEffectOptions.md#delay)
132 |
133 | #### Defined in
134 |
135 | node_modules/typescript/lib/lib.dom.d.ts:444
136 |
137 | ___
138 |
139 | ### duration
140 |
141 | • `Optional` **duration**: `string` \| `number` \| `CSSNumericValue`
142 |
143 | #### Inherited from
144 |
145 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[duration](TypedKeyframeEffectOptions.md#duration)
146 |
147 | #### Defined in
148 |
149 | node_modules/typescript/lib/lib.dom.d.ts:446
150 |
151 | ___
152 |
153 | ### endDelay
154 |
155 | • `Optional` **endDelay**: `number`
156 |
157 | #### Inherited from
158 |
159 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[endDelay](TypedKeyframeEffectOptions.md#enddelay)
160 |
161 | #### Defined in
162 |
163 | node_modules/typescript/lib/lib.dom.d.ts:448
164 |
165 | ___
166 |
167 | ### iterationStart
168 |
169 | • `Optional` **iterationStart**: `number`
170 |
171 | #### Inherited from
172 |
173 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationStart](TypedKeyframeEffectOptions.md#iterationstart)
174 |
175 | #### Defined in
176 |
177 | node_modules/typescript/lib/lib.dom.d.ts:450
178 |
179 | ___
180 |
181 | ### iterations
182 |
183 | • `Optional` **iterations**: `number`
184 |
185 | #### Inherited from
186 |
187 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterations](TypedKeyframeEffectOptions.md#iterations)
188 |
189 | #### Defined in
190 |
191 | node_modules/typescript/lib/lib.dom.d.ts:451
192 |
193 | ___
194 |
195 | ### playbackRate
196 |
197 | • `Optional` **playbackRate**: `number`
198 |
199 | #### Inherited from
200 |
201 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[playbackRate](TypedKeyframeEffectOptions.md#playbackrate)
202 |
203 | #### Defined in
204 |
205 | node_modules/typescript/lib/lib.dom.d.ts:452
206 |
--------------------------------------------------------------------------------
/docs/interfaces/BaseAnimationHandle.md:
--------------------------------------------------------------------------------
1 | # Interface: BaseAnimationHandle
2 |
3 | ## Type parameters
4 |
5 | | Name | Type |
6 | | :------ | :------ |
7 | | `Args` | `void` |
8 |
9 | ## Hierarchy
10 |
11 | - **`BaseAnimationHandle`**
12 |
13 | ↳ [`AnimationHandle`](AnimationHandle.md)
14 |
15 | ↳ [`AnimationFunctionHandle`](AnimationFunctionHandle.md)
16 |
17 | ## Table of contents
18 |
19 | ### Methods
20 |
21 | - [play](BaseAnimationHandle.md#play)
22 | - [reverse](BaseAnimationHandle.md#reverse)
23 | - [cancel](BaseAnimationHandle.md#cancel)
24 | - [finish](BaseAnimationHandle.md#finish)
25 | - [pause](BaseAnimationHandle.md#pause)
26 | - [setTime](BaseAnimationHandle.md#settime)
27 | - [setPlaybackRate](BaseAnimationHandle.md#setplaybackrate)
28 | - [waitFor](BaseAnimationHandle.md#waitfor)
29 |
30 | ## Methods
31 |
32 | ### play
33 |
34 | ▸ **play**(`...opts`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
35 |
36 | A wrapper of Web Animations API's [play](https://developer.mozilla.org/en-US/docs/Web/API/Animation/play). It's similar to GSAP's `play()`.
37 |
38 | #### Parameters
39 |
40 | | Name | Type |
41 | | :------ | :------ |
42 | | `...opts` | `Args` extends `void` ? [PlayOptions?] : [[`PlayOptionsWithArgs`](../API.md#playoptionswithargs)<`Args`\>] |
43 |
44 | #### Returns
45 |
46 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
47 |
48 | #### Defined in
49 |
50 | [src/react/hooks/useAnimation.ts:44](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L44)
51 |
52 | ___
53 |
54 | ### reverse
55 |
56 | ▸ **reverse**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
57 |
58 | A wrapper of Web Animations API's [reverse](https://developer.mozilla.org/en-US/docs/Web/API/Animation/reverse). It's similar to GSAP's `reverse()`.
59 |
60 | #### Returns
61 |
62 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
63 |
64 | #### Defined in
65 |
66 | [src/react/hooks/useAnimation.ts:50](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L50)
67 |
68 | ___
69 |
70 | ### cancel
71 |
72 | ▸ **cancel**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
73 |
74 | A wrapper of Web Animations API's [cancel](https://developer.mozilla.org/en-US/docs/Web/API/Animation/cancel). It's similar to GSAP's `kill()`.
75 |
76 | #### Returns
77 |
78 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
79 |
80 | #### Defined in
81 |
82 | [src/react/hooks/useAnimation.ts:54](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L54)
83 |
84 | ___
85 |
86 | ### finish
87 |
88 | ▸ **finish**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
89 |
90 | A wrapper of Web Animations API's [finish](https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish).
91 |
92 | #### Returns
93 |
94 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
95 |
96 | #### Defined in
97 |
98 | [src/react/hooks/useAnimation.ts:58](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L58)
99 |
100 | ___
101 |
102 | ### pause
103 |
104 | ▸ **pause**(): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
105 |
106 | A wrapper of Web Animations API's [pause](https://developer.mozilla.org/en-US/docs/Web/API/Animation/pause). It's similar to GSAP's `pause()`.
107 |
108 | #### Returns
109 |
110 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
111 |
112 | #### Defined in
113 |
114 | [src/react/hooks/useAnimation.ts:62](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L62)
115 |
116 | ___
117 |
118 | ### setTime
119 |
120 | ▸ **setTime**(`time`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
121 |
122 | A setter of Web Animations API's [currentTime](https://developer.mozilla.org/en-US/docs/Web/API/Animation/currentTime). It's similar to GSAP's `seek()`.
123 |
124 | If you pass function, you can get [endTime](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming#return_value) from its argument.
125 |
126 | #### Parameters
127 |
128 | | Name | Type |
129 | | :------ | :------ |
130 | | `time` | `number` \| (`endTime`: `number`) => `number` |
131 |
132 | #### Returns
133 |
134 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
135 |
136 | #### Defined in
137 |
138 | [src/react/hooks/useAnimation.ts:68](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L68)
139 |
140 | ___
141 |
142 | ### setPlaybackRate
143 |
144 | ▸ **setPlaybackRate**(`rate`): [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
145 |
146 | A wrapper of Web Animations API's [updatePlaybackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/updatePlaybackRate). It's similar to GSAP's `timeScale()`.
147 |
148 | If you pass function, you can get current [playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate) from its argument.
149 |
150 | #### Parameters
151 |
152 | | Name | Type |
153 | | :------ | :------ |
154 | | `rate` | `number` \| (`prevRate`: `number`) => `number` |
155 |
156 | #### Returns
157 |
158 | [`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>
159 |
160 | #### Defined in
161 |
162 | [src/react/hooks/useAnimation.ts:76](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L76)
163 |
164 | ___
165 |
166 | ### waitFor
167 |
168 | ▸ **waitFor**(`event`): `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
169 |
170 | A getter of Promise that will be resolved in specified timing.
171 |
172 | - `finished`: resolved when animation is finished and its playback direction is normal.
173 | - `reverseFinished`: resolved when animation is finished and its playback direction is reversed.
174 |
175 | #### Parameters
176 |
177 | | Name | Type |
178 | | :------ | :------ |
179 | | `event` | [`WaitingAnimationEventName`](../API.md#waitinganimationeventname) |
180 |
181 | #### Returns
182 |
183 | `Promise`<[`BaseAnimationHandle`](BaseAnimationHandle.md)<`Args`\>\>
184 |
185 | #### Defined in
186 |
187 | [src/react/hooks/useAnimation.ts:85](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useAnimation.ts#L85)
188 |
--------------------------------------------------------------------------------
/docs/interfaces/ScrollTimelineOpts.md:
--------------------------------------------------------------------------------
1 | # Interface: ScrollTimelineOpts
2 |
3 | ## Table of contents
4 |
5 | ### Properties
6 |
7 | - [axis](ScrollTimelineOpts.md#axis)
8 |
9 | ## Properties
10 |
11 | ### axis
12 |
13 | • `Optional` **axis**: [`ScrollTimelineAxis`](../API.md#scrolltimelineaxis)
14 |
15 | #### Defined in
16 |
17 | [src/react/hooks/useScrollTimeline.ts:8](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useScrollTimeline.ts#L8)
18 |
--------------------------------------------------------------------------------
/docs/interfaces/TimelineDefinition.md:
--------------------------------------------------------------------------------
1 | # Interface: TimelineDefinition
2 |
3 | ## Callable
4 |
5 | ### TimelineDefinition
6 |
7 | ▸ **TimelineDefinition**(`el`): `void`
8 |
9 | #### Parameters
10 |
11 | | Name | Type |
12 | | :------ | :------ |
13 | | `el` | ``null`` \| `Element` |
14 |
15 | #### Returns
16 |
17 | `void`
18 |
19 | #### Defined in
20 |
21 | [src/react/types/index.ts:18](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/types/index.ts#L18)
22 |
--------------------------------------------------------------------------------
/docs/interfaces/TransitionAnimationHandle.md:
--------------------------------------------------------------------------------
1 | # Interface: TransitionAnimationHandle
2 |
3 | ## Callable
4 |
5 | ### TransitionAnimationHandle
6 |
7 | ▸ **TransitionAnimationHandle**(`ref`): `void`
8 |
9 | #### Parameters
10 |
11 | | Name | Type |
12 | | :------ | :------ |
13 | | `ref` | ``null`` \| `Element` |
14 |
15 | #### Returns
16 |
17 | `void`
18 |
19 | #### Defined in
20 |
21 | [src/react/hooks/useTransitionAnimation.ts:22](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useTransitionAnimation.ts#L22)
22 |
--------------------------------------------------------------------------------
/docs/interfaces/TransitionAnimationOptions.md:
--------------------------------------------------------------------------------
1 | # Interface: TransitionAnimationOptions
2 |
3 | Extended [options of KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect)
4 |
5 | ## Hierarchy
6 |
7 | - [`TypedKeyframeEffectOptions`](TypedKeyframeEffectOptions.md)
8 |
9 | ↳ **`TransitionAnimationOptions`**
10 |
11 | ## Table of contents
12 |
13 | ### Properties
14 |
15 | - [easing](TransitionAnimationOptions.md#easing)
16 | - [fill](TransitionAnimationOptions.md#fill)
17 | - [direction](TransitionAnimationOptions.md#direction)
18 | - [composite](TransitionAnimationOptions.md#composite)
19 | - [iterationComposite](TransitionAnimationOptions.md#iterationcomposite)
20 | - [pseudoElement](TransitionAnimationOptions.md#pseudoelement)
21 | - [delay](TransitionAnimationOptions.md#delay)
22 | - [duration](TransitionAnimationOptions.md#duration)
23 | - [endDelay](TransitionAnimationOptions.md#enddelay)
24 | - [iterationStart](TransitionAnimationOptions.md#iterationstart)
25 | - [iterations](TransitionAnimationOptions.md#iterations)
26 | - [playbackRate](TransitionAnimationOptions.md#playbackrate)
27 |
28 | ## Properties
29 |
30 | ### easing
31 |
32 | • `Optional` **easing**: [`TypedEasing`](../API.md#typedeasing)
33 |
34 | #### Inherited from
35 |
36 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[easing](TypedKeyframeEffectOptions.md#easing)
37 |
38 | #### Defined in
39 |
40 | [src/core/waapi.ts:41](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L41)
41 |
42 | ___
43 |
44 | ### fill
45 |
46 | • `Optional` **fill**: `FillMode`
47 |
48 | #### Inherited from
49 |
50 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[fill](TypedKeyframeEffectOptions.md#fill)
51 |
52 | #### Defined in
53 |
54 | node_modules/typescript/lib/lib.dom.d.ts:449
55 |
56 | ___
57 |
58 | ### direction
59 |
60 | • `Optional` **direction**: `PlaybackDirection`
61 |
62 | #### Inherited from
63 |
64 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[direction](TypedKeyframeEffectOptions.md#direction)
65 |
66 | #### Defined in
67 |
68 | node_modules/typescript/lib/lib.dom.d.ts:445
69 |
70 | ___
71 |
72 | ### composite
73 |
74 | • `Optional` **composite**: `CompositeOperation`
75 |
76 | #### Inherited from
77 |
78 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[composite](TypedKeyframeEffectOptions.md#composite)
79 |
80 | #### Defined in
81 |
82 | node_modules/typescript/lib/lib.dom.d.ts:757
83 |
84 | ___
85 |
86 | ### iterationComposite
87 |
88 | • `Optional` **iterationComposite**: `IterationCompositeOperation`
89 |
90 | #### Inherited from
91 |
92 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationComposite](TypedKeyframeEffectOptions.md#iterationcomposite)
93 |
94 | #### Defined in
95 |
96 | node_modules/typescript/lib/lib.dom.d.ts:758
97 |
98 | ___
99 |
100 | ### pseudoElement
101 |
102 | • `Optional` **pseudoElement**: ``null`` \| `string`
103 |
104 | #### Inherited from
105 |
106 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[pseudoElement](TypedKeyframeEffectOptions.md#pseudoelement)
107 |
108 | #### Defined in
109 |
110 | node_modules/typescript/lib/lib.dom.d.ts:759
111 |
112 | ___
113 |
114 | ### delay
115 |
116 | • `Optional` **delay**: `number`
117 |
118 | #### Inherited from
119 |
120 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[delay](TypedKeyframeEffectOptions.md#delay)
121 |
122 | #### Defined in
123 |
124 | node_modules/typescript/lib/lib.dom.d.ts:444
125 |
126 | ___
127 |
128 | ### duration
129 |
130 | • `Optional` **duration**: `string` \| `number` \| `CSSNumericValue`
131 |
132 | #### Inherited from
133 |
134 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[duration](TypedKeyframeEffectOptions.md#duration)
135 |
136 | #### Defined in
137 |
138 | node_modules/typescript/lib/lib.dom.d.ts:446
139 |
140 | ___
141 |
142 | ### endDelay
143 |
144 | • `Optional` **endDelay**: `number`
145 |
146 | #### Inherited from
147 |
148 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[endDelay](TypedKeyframeEffectOptions.md#enddelay)
149 |
150 | #### Defined in
151 |
152 | node_modules/typescript/lib/lib.dom.d.ts:448
153 |
154 | ___
155 |
156 | ### iterationStart
157 |
158 | • `Optional` **iterationStart**: `number`
159 |
160 | #### Inherited from
161 |
162 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterationStart](TypedKeyframeEffectOptions.md#iterationstart)
163 |
164 | #### Defined in
165 |
166 | node_modules/typescript/lib/lib.dom.d.ts:450
167 |
168 | ___
169 |
170 | ### iterations
171 |
172 | • `Optional` **iterations**: `number`
173 |
174 | #### Inherited from
175 |
176 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[iterations](TypedKeyframeEffectOptions.md#iterations)
177 |
178 | #### Defined in
179 |
180 | node_modules/typescript/lib/lib.dom.d.ts:451
181 |
182 | ___
183 |
184 | ### playbackRate
185 |
186 | • `Optional` **playbackRate**: `number`
187 |
188 | #### Inherited from
189 |
190 | [TypedKeyframeEffectOptions](TypedKeyframeEffectOptions.md).[playbackRate](TypedKeyframeEffectOptions.md#playbackrate)
191 |
192 | #### Defined in
193 |
194 | node_modules/typescript/lib/lib.dom.d.ts:452
195 |
--------------------------------------------------------------------------------
/docs/interfaces/TransitionGroupProps.md:
--------------------------------------------------------------------------------
1 | # Interface: TransitionGroupProps
2 |
3 | ## Table of contents
4 |
5 | ### Properties
6 |
7 | - [children](TransitionGroupProps.md#children)
8 |
9 | ## Properties
10 |
11 | ### children
12 |
13 | • **children**: `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> \| `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\>[]
14 |
15 | #### Defined in
16 |
17 | [src/react/components/TransitionGroup.tsx:75](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/components/TransitionGroup.tsx#L75)
18 |
--------------------------------------------------------------------------------
/docs/interfaces/TypedKeyframeEffectOptions.md:
--------------------------------------------------------------------------------
1 | # Interface: TypedKeyframeEffectOptions
2 |
3 | Extended [options of KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect)
4 |
5 | ## Hierarchy
6 |
7 | - `Omit`<`KeyframeEffectOptions`, ``"easing"``\>
8 |
9 | ↳ **`TypedKeyframeEffectOptions`**
10 |
11 | ↳↳ [`AnimationFunctionOptions`](AnimationFunctionOptions.md)
12 |
13 | ↳↳ [`TransitionAnimationOptions`](TransitionAnimationOptions.md)
14 |
15 | ↳↳ [`AnimationOptions`](AnimationOptions.md)
16 |
17 | ## Table of contents
18 |
19 | ### Properties
20 |
21 | - [easing](TypedKeyframeEffectOptions.md#easing)
22 | - [fill](TypedKeyframeEffectOptions.md#fill)
23 | - [direction](TypedKeyframeEffectOptions.md#direction)
24 | - [composite](TypedKeyframeEffectOptions.md#composite)
25 | - [iterationComposite](TypedKeyframeEffectOptions.md#iterationcomposite)
26 | - [pseudoElement](TypedKeyframeEffectOptions.md#pseudoelement)
27 | - [delay](TypedKeyframeEffectOptions.md#delay)
28 | - [duration](TypedKeyframeEffectOptions.md#duration)
29 | - [endDelay](TypedKeyframeEffectOptions.md#enddelay)
30 | - [iterationStart](TypedKeyframeEffectOptions.md#iterationstart)
31 | - [iterations](TypedKeyframeEffectOptions.md#iterations)
32 | - [playbackRate](TypedKeyframeEffectOptions.md#playbackrate)
33 |
34 | ## Properties
35 |
36 | ### easing
37 |
38 | • `Optional` **easing**: [`TypedEasing`](../API.md#typedeasing)
39 |
40 | #### Defined in
41 |
42 | [src/core/waapi.ts:41](https://github.com/inokawa/react-animatable/blob/3ce964a/src/core/waapi.ts#L41)
43 |
44 | ___
45 |
46 | ### fill
47 |
48 | • `Optional` **fill**: `FillMode`
49 |
50 | #### Inherited from
51 |
52 | Omit.fill
53 |
54 | #### Defined in
55 |
56 | node_modules/typescript/lib/lib.dom.d.ts:449
57 |
58 | ___
59 |
60 | ### direction
61 |
62 | • `Optional` **direction**: `PlaybackDirection`
63 |
64 | #### Inherited from
65 |
66 | Omit.direction
67 |
68 | #### Defined in
69 |
70 | node_modules/typescript/lib/lib.dom.d.ts:445
71 |
72 | ___
73 |
74 | ### composite
75 |
76 | • `Optional` **composite**: `CompositeOperation`
77 |
78 | #### Inherited from
79 |
80 | Omit.composite
81 |
82 | #### Defined in
83 |
84 | node_modules/typescript/lib/lib.dom.d.ts:757
85 |
86 | ___
87 |
88 | ### iterationComposite
89 |
90 | • `Optional` **iterationComposite**: `IterationCompositeOperation`
91 |
92 | #### Inherited from
93 |
94 | Omit.iterationComposite
95 |
96 | #### Defined in
97 |
98 | node_modules/typescript/lib/lib.dom.d.ts:758
99 |
100 | ___
101 |
102 | ### pseudoElement
103 |
104 | • `Optional` **pseudoElement**: ``null`` \| `string`
105 |
106 | #### Inherited from
107 |
108 | Omit.pseudoElement
109 |
110 | #### Defined in
111 |
112 | node_modules/typescript/lib/lib.dom.d.ts:759
113 |
114 | ___
115 |
116 | ### delay
117 |
118 | • `Optional` **delay**: `number`
119 |
120 | #### Inherited from
121 |
122 | Omit.delay
123 |
124 | #### Defined in
125 |
126 | node_modules/typescript/lib/lib.dom.d.ts:444
127 |
128 | ___
129 |
130 | ### duration
131 |
132 | • `Optional` **duration**: `string` \| `number` \| `CSSNumericValue`
133 |
134 | #### Inherited from
135 |
136 | Omit.duration
137 |
138 | #### Defined in
139 |
140 | node_modules/typescript/lib/lib.dom.d.ts:446
141 |
142 | ___
143 |
144 | ### endDelay
145 |
146 | • `Optional` **endDelay**: `number`
147 |
148 | #### Inherited from
149 |
150 | Omit.endDelay
151 |
152 | #### Defined in
153 |
154 | node_modules/typescript/lib/lib.dom.d.ts:448
155 |
156 | ___
157 |
158 | ### iterationStart
159 |
160 | • `Optional` **iterationStart**: `number`
161 |
162 | #### Inherited from
163 |
164 | Omit.iterationStart
165 |
166 | #### Defined in
167 |
168 | node_modules/typescript/lib/lib.dom.d.ts:450
169 |
170 | ___
171 |
172 | ### iterations
173 |
174 | • `Optional` **iterations**: `number`
175 |
176 | #### Inherited from
177 |
178 | Omit.iterations
179 |
180 | #### Defined in
181 |
182 | node_modules/typescript/lib/lib.dom.d.ts:451
183 |
184 | ___
185 |
186 | ### playbackRate
187 |
188 | • `Optional` **playbackRate**: `number`
189 |
190 | #### Inherited from
191 |
192 | Omit.playbackRate
193 |
194 | #### Defined in
195 |
196 | node_modules/typescript/lib/lib.dom.d.ts:452
197 |
--------------------------------------------------------------------------------
/docs/interfaces/ViewTimelineOpts.md:
--------------------------------------------------------------------------------
1 | # Interface: ViewTimelineOpts
2 |
3 | ## Table of contents
4 |
5 | ### Properties
6 |
7 | - [axis](ViewTimelineOpts.md#axis)
8 | - [inset](ViewTimelineOpts.md#inset)
9 |
10 | ## Properties
11 |
12 | ### axis
13 |
14 | • `Optional` **axis**: [`ScrollTimelineAxis`](../API.md#scrolltimelineaxis)
15 |
16 | #### Defined in
17 |
18 | [src/react/hooks/useViewTimeline.ts:12](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useViewTimeline.ts#L12)
19 |
20 | ___
21 |
22 | ### inset
23 |
24 | • `Optional` **inset**: [`ViewTimelineInset`](../API.md#viewtimelineinset)
25 |
26 | #### Defined in
27 |
28 | [src/react/hooks/useViewTimeline.ts:13](https://github.com/inokawa/react-animatable/blob/3ce964a/src/react/hooks/useViewTimeline.ts#L13)
29 |
--------------------------------------------------------------------------------
/images/demo-chart.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inokawa/react-animatable/67622b5b57c1721c8aaed8317e9d4a1e7fe1b459/images/demo-chart.gif
--------------------------------------------------------------------------------
/images/demo-scroll.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inokawa/react-animatable/67622b5b57c1721c8aaed8317e9d4a1e7fe1b459/images/demo-scroll.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-animatable",
3 | "version": "0.15.2",
4 | "description": "Tiny(~1kB) animation hooks for React, built on Web Animations API.",
5 | "main": "lib/index.js",
6 | "module": "lib/index.mjs",
7 | "types": "lib/index.d.ts",
8 | "exports": {
9 | "./package.json": "./package.json",
10 | ".": {
11 | "types": "./lib/index.d.ts",
12 | "import": "./lib/index.mjs",
13 | "default": "./lib/index.js"
14 | }
15 | },
16 | "files": [
17 | "lib"
18 | ],
19 | "sideEffects": false,
20 | "scripts": {
21 | "build": "rollup -c",
22 | "tsc": "tsc -p . --noEmit",
23 | "test": "vitest run",
24 | "storybook": "storybook dev -p 6006",
25 | "storybook:build": "storybook build",
26 | "storybook:test": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"npm run storybook -- --no-open\" \"wait-on tcp:6006 && test-storybook\"",
27 | "typedoc": "typedoc",
28 | "size": "size-limit",
29 | "prepublishOnly": "npm run typedoc && rimraf lib && npm run build"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.22.11",
33 | "@babel/plugin-transform-react-pure-annotations": "^7.22.5",
34 | "@chakra-ui/react": "^2.8.2",
35 | "@emotion/css": "^11.10.5",
36 | "@emotion/react": "^11.10.5",
37 | "@emotion/styled": "^11.10.5",
38 | "@fluentui/react": "^8.103.3",
39 | "@linaria/babel-preset": "^4.3.0",
40 | "@linaria/core": "^4.2.8",
41 | "@linaria/react": "^4.3.6",
42 | "@linaria/webpack-loader": "^4.1.15",
43 | "@mantine/core": "^5.8.4",
44 | "@mantine/hooks": "^5.8.4",
45 | "@mui/material": "^5.10.16",
46 | "@rollup/plugin-babel": "^6.0.4",
47 | "@rollup/plugin-terser": "^0.4.4",
48 | "@rollup/plugin-typescript": "^11.1.6",
49 | "@size-limit/preset-small-lib": "^11.1.2",
50 | "@storybook/addon-storysource": "^8.4.0",
51 | "@storybook/addon-webpack5-compiler-swc": "^1.0.2",
52 | "@storybook/react": "^8.4.0",
53 | "@storybook/react-webpack5": "^8.4.0",
54 | "@storybook/source-loader": "^8.4.0",
55 | "@storybook/test-runner": "^0.20.0",
56 | "@types/react": "^18.3.1",
57 | "@types/react-dom": "^18.3.0",
58 | "@vanilla-extract/css": "^1.9.2",
59 | "@vanilla-extract/webpack-plugin": "^2.2.0",
60 | "antd": "^5.0.2",
61 | "babel-loader": "^9.1.3",
62 | "concurrently": "^7.2.2",
63 | "framer-motion": "^10.16.16",
64 | "jsdom": "^25.0.1",
65 | "mini-css-extract-plugin": "^2.7.7",
66 | "react": "^18.3.1",
67 | "react-dom": "^18.3.1",
68 | "react-merge-refs": "^2.0.2",
69 | "rimraf": "^5.0.5",
70 | "rollup": "^4.17.2",
71 | "rollup-plugin-banner2": "^1.2.3",
72 | "size-limit": "^11.1.2",
73 | "storybook": "^8.4.0",
74 | "styled-components": "^6.0.7",
75 | "typedoc": "^0.27.0",
76 | "typedoc-plugin-markdown": "^3.16.0",
77 | "typescript": "^5.4.5",
78 | "vitest": "^2.1.4",
79 | "wait-on": "^7.0.1",
80 | "web-animations-js": "^2.3.2",
81 | "webpack": "^5.88.2"
82 | },
83 | "peerDependencies": {
84 | "react": ">=16.14.0"
85 | },
86 | "repository": {
87 | "type": "git",
88 | "url": "git+https://github.com/inokawa/react-animatable.git"
89 | },
90 | "keywords": [
91 | "react",
92 | "react-component",
93 | "react-hooks",
94 | "hooks",
95 | "animation",
96 | "tween",
97 | "transition",
98 | "timeline",
99 | "scroll",
100 | "view",
101 | "visualization",
102 | "svg",
103 | "css",
104 | "canvas",
105 | "d3",
106 | "web-animations"
107 | ],
108 | "author": "inokawa (https://github.com/inokawa/)",
109 | "license": "MIT",
110 | "bugs": {
111 | "url": "https://github.com/inokawa/react-animatable/issues"
112 | },
113 | "homepage": "https://github.com/inokawa/react-animatable#readme"
114 | }
115 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 | import { getBabelOutputPlugin } from "@rollup/plugin-babel";
3 | import terser from "@rollup/plugin-terser";
4 | import banner from "rollup-plugin-banner2";
5 | import pkg from "./package.json" with { type: "json" };
6 |
7 | const keys = (p) => Object.keys(p || {});
8 |
9 | export default {
10 | input: "src/index.ts",
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: "cjs",
15 | sourcemap: true,
16 | },
17 | {
18 | file: pkg.module,
19 | format: "es",
20 | sourcemap: true,
21 | },
22 | ],
23 | external: (id) =>
24 | [...keys(pkg.dependencies), ...keys(pkg.devDependencies)].some((d) =>
25 | id.startsWith(d)
26 | ),
27 | plugins: [
28 | typescript({
29 | tsconfig: "./tsconfig.json",
30 | outDir: ".",
31 | declaration: true,
32 | exclude: ["src/**/*.spec.*"],
33 | }),
34 | getBabelOutputPlugin({
35 | plugins: ["@babel/plugin-transform-react-pure-annotations"],
36 | }),
37 | terser({
38 | ecma: 2015,
39 | module: true,
40 | compress: { passes: 5, unsafe: true, keep_fargs: false },
41 | mangle: { properties: { regex: "^_" } },
42 | format: {
43 | preserve_annotations: true,
44 | },
45 | }),
46 | banner(() => '"use client";\n'),
47 | ],
48 | };
49 |
--------------------------------------------------------------------------------
/src/@types/index.d.ts:
--------------------------------------------------------------------------------
1 | // TODO replace if added to lib.dom.d.ts
2 | declare var ScrollTimeline: {
3 | prototype: AnimationTimeline;
4 | new (opts: {
5 | source: Element;
6 | axis?: "block" | "inline" | "y" | "x" | undefined;
7 | }): AnimationTimeline;
8 | };
9 |
10 | // TODO replace if added to lib.dom.d.ts
11 | declare var ViewTimeline: {
12 | prototype: AnimationTimeline;
13 | new (opts: {
14 | subject: Element;
15 | axis?: "block" | "inline" | "y" | "x" | undefined;
16 | inset?: "auto" | (string & {}) | undefined;
17 | }): AnimationTimeline;
18 | };
19 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export type {
2 | TypedKeyframe,
3 | TypedEasing,
4 | GetKeyframeFunction,
5 | TypedKeyframeEffectOptions,
6 | AnimatableCSSProperties,
7 | PlayOptions,
8 | WaitingAnimationEventName,
9 | } from "./waapi";
10 |
--------------------------------------------------------------------------------
/src/core/utils.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { isSameObject, isSameObjectArray } from "./utils";
3 |
4 | describe("isSameObject", () => {
5 | it("should return true if the objects are same", () => {
6 | expect(
7 | isSameObject(
8 | { transform: "translateX(0px)", fill: "lightskyblue" },
9 | { transform: "translateX(0px)", fill: "lightskyblue" }
10 | )
11 | ).toBe(true);
12 | });
13 |
14 | it("should return true if the fields order is changed", () => {
15 | expect(
16 | isSameObject(
17 | { transform: "translateX(0px)", fill: "lightskyblue" },
18 | { fill: "lightskyblue", transform: "translateX(0px)" }
19 | )
20 | ).toBe(true);
21 | });
22 |
23 | it("should return true if the objects are empty", () => {
24 | expect(isSameObject({}, {})).toBe(true);
25 | });
26 |
27 | it("should return false if a field is changed", () => {
28 | expect(
29 | isSameObject(
30 | { transform: "translateX(0px)", fill: "lightskyblue" },
31 | { transform: "translateX(100px)", fill: "lightskyblue" }
32 | )
33 | ).toBe(false);
34 | });
35 |
36 | it("should return false if a field name is changed", () => {
37 | expect(
38 | isSameObject(
39 | { transform: "translateX(0px)", fill: "lightskyblue" },
40 | { transform: "translateX(0px)", color: "lightskyblue" }
41 | )
42 | ).toBe(false);
43 | });
44 |
45 | it("should return false if a field is added", () => {
46 | expect(
47 | isSameObject(
48 | { transform: "translateX(0px)", fill: "lightskyblue" },
49 | { transform: "translateX(0px)", fill: "lightskyblue", color: "red" }
50 | )
51 | ).toBe(false);
52 | });
53 |
54 | it("should return false if a field is deleted", () => {
55 | expect(
56 | isSameObject(
57 | { transform: "translateX(0px)", fill: "lightskyblue" },
58 | { transform: "translateX(0px)" }
59 | )
60 | ).toBe(false);
61 | });
62 | });
63 |
64 | describe("isSameObjectArray", () => {
65 | it("should return true if the arrays are same", () => {
66 | expect(
67 | isSameObjectArray(
68 | [
69 | { transform: "translateX(0px)", fill: "lightskyblue" },
70 | { transform: "translateX(100px)", fill: "red" },
71 | ],
72 | [
73 | { transform: "translateX(0px)", fill: "lightskyblue" },
74 | { transform: "translateX(100px)", fill: "red" },
75 | ]
76 | )
77 | ).toBe(true);
78 | });
79 |
80 | it("should return true if the arrays are empty", () => {
81 | expect(isSameObjectArray([], [])).toBe(true);
82 | });
83 |
84 | it("should return false if a field is updated", () => {
85 | expect(
86 | isSameObjectArray(
87 | [
88 | { transform: "translateX(0px)", fill: "lightskyblue" },
89 | { transform: "translateX(100px)", fill: "red" },
90 | ],
91 | [
92 | { transform: "translateX(0px)", fill: "lightskyblue" },
93 | { transform: "translateX(50px)", fill: "red" },
94 | ]
95 | )
96 | ).toBe(false);
97 | });
98 |
99 | it("should return false if a field is deleted", () => {
100 | expect(
101 | isSameObjectArray(
102 | [
103 | { transform: "translateX(0px)", fill: "lightskyblue" },
104 | { transform: "translateX(100px)", fill: "red" },
105 | ],
106 | [
107 | { transform: "translateX(0px)", fill: "lightskyblue" },
108 | { transform: "translateX(100px)" },
109 | ]
110 | )
111 | ).toBe(false);
112 | });
113 |
114 | it("should return false if a field is added", () => {
115 | expect(
116 | isSameObjectArray(
117 | [
118 | { transform: "translateX(0px)", fill: "lightskyblue" },
119 | { transform: "translateX(100px)", fill: "red" },
120 | ],
121 | [
122 | { transform: "translateX(0px)", fill: "lightskyblue" },
123 | { transform: "translateX(100px)", fill: "red", color: "blue" },
124 | ]
125 | )
126 | ).toBe(false);
127 | });
128 |
129 | it("should return false if an object is added", () => {
130 | expect(
131 | isSameObjectArray(
132 | [
133 | { transform: "translateX(0px)", fill: "lightskyblue" },
134 | { transform: "translateX(100px)", fill: "red" },
135 | ],
136 | [
137 | { transform: "translateX(0px)", fill: "lightskyblue" },
138 | { transform: "translateX(100px)", fill: "red" },
139 | { transform: "translateX(100px)", fill: "red" },
140 | ]
141 | )
142 | ).toBe(false);
143 | });
144 |
145 | it("should return false if an object is added", () => {
146 | expect(
147 | isSameObjectArray(
148 | [
149 | { transform: "translateX(0px)", fill: "lightskyblue" },
150 | { transform: "translateX(100px)", fill: "red" },
151 | ],
152 | [{ transform: "translateX(0px)", fill: "lightskyblue" }]
153 | )
154 | ).toBe(false);
155 | });
156 |
157 | it("should return false if the object order is changed", () => {
158 | expect(
159 | isSameObjectArray(
160 | [
161 | { transform: "translateX(0px)", fill: "lightskyblue" },
162 | { transform: "translateX(100px)", fill: "red" },
163 | ],
164 | [
165 | { transform: "translateX(100px)", fill: "red" },
166 | { transform: "translateX(0px)", fill: "lightskyblue" },
167 | ]
168 | )
169 | ).toBe(false);
170 | });
171 | });
172 |
--------------------------------------------------------------------------------
/src/core/utils.ts:
--------------------------------------------------------------------------------
1 | /** @internal */
2 | export const noop = () => {};
3 | /** @internal */
4 | export const getKeys: (item: T) => (keyof T)[] = Object.keys;
5 | /** @internal */
6 | export const assign = Object.assign;
7 | /** @internal */
8 | export const isArray = Array.isArray;
9 | /** @internal */
10 | export const getStyle = (e: Element) => getComputedStyle(e);
11 |
12 | /**
13 | * @internal
14 | */
15 | export const isSameObject = (
16 | target: object = {},
17 | prev: object = {}
18 | ): boolean => {
19 | const keys = getKeys(target);
20 | if (keys.length !== getKeys(prev).length) return false;
21 | return keys.every((k) => (target as any)[k] === (prev as any)[k]);
22 | };
23 |
24 | /**
25 | * @internal
26 | */
27 | export const isSameObjectArray = (
28 | target: object[],
29 | prev: object[]
30 | ): boolean => {
31 | if (target.length !== prev.length) return false;
32 | return target.every((t, i) => isSameObject(t, prev[i]));
33 | };
34 |
35 | /**
36 | * @internal
37 | */
38 | export const toArray = (items: T | T[]): T[] =>
39 | isArray(items) ? items : [items];
40 |
41 | /**
42 | * @internal
43 | */
44 | export const uniq = (items: T[]): T[] => {
45 | return Array.from(new Set(items));
46 | };
47 |
--------------------------------------------------------------------------------
/src/core/waapi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { getKeyframeKeys } from "./waapi";
3 |
4 | describe("getKeyframeKeys", () => {
5 | it("normal", () => {
6 | expect(
7 | getKeyframeKeys([
8 | { transform: "rotate(-720deg) translateX(0px)" },
9 | { transform: "rotate(-360deg) translateX(-250px)", offset: 0.25 },
10 | {
11 | transform: "rotate(0deg) translateX(0px)",
12 | fill: "red",
13 | fontSize: "48px",
14 | fontWeight: "bold",
15 | offset: 0.75,
16 | },
17 | { transform: "rotate(360deg) translateX(0px)", fill: "lightskyblue" },
18 | ])
19 | ).toEqual(["transform", "fill", "fontSize", "fontWeight"]);
20 | });
21 |
22 | it("ignore offset, easing and composite", () => {
23 | expect(
24 | getKeyframeKeys([
25 | { transform: "rotate(-720deg) translateX(0px)", easing: "ease" },
26 | {
27 | transform: "rotate(0deg) translateX(0px)",
28 | offset: 0.75,
29 | composite: "accumulate",
30 | },
31 | { transform: "rotate(360deg) translateX(0px)" },
32 | ])
33 | ).toEqual(["transform"]);
34 | });
35 |
36 | it("map cssFloat and cssOffset", () => {
37 | expect(getKeyframeKeys([{ cssFloat: "left", cssOffset: 10 }])).toEqual([
38 | "float",
39 | "offset",
40 | ]);
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/core/waapi.ts:
--------------------------------------------------------------------------------
1 | import type { CSSProperties } from "react";
2 | import { getKeys, uniq } from "./utils";
3 |
4 | export type AnimatableCSSProperties = Omit<
5 | CSSProperties,
6 | "offset" | "float"
7 | > & {
8 | cssOffset?: CSSProperties["offset"];
9 | cssFloat?: CSSProperties["float"];
10 | // CSS Motion Path for SVG
11 | d?: string;
12 | };
13 |
14 | /**
15 | * Strictly typed [Keyframe](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats)
16 | */
17 | export type TypedKeyframe = Pick<
18 | Keyframe,
19 | "composite" | "easing" | "offset"
20 | > & { [key: `--${string}`]: string | number } & AnimatableCSSProperties;
21 |
22 | export type TypedEasing = Exclude<
23 | CSSProperties["animationTimingFunction"],
24 | CSSProperties["all"] | undefined
25 | >;
26 |
27 | /**
28 | * A function to define keyframe dynamically
29 | * - `prev`: current style
30 | * - `args`: any argument passed from play
31 | */
32 | export type GetKeyframeFunction = Args extends void
33 | ? (prev: CSSStyleDeclaration) => TypedKeyframe[]
34 | : (prev: CSSStyleDeclaration, args: Args) => TypedKeyframe[];
35 |
36 | /**
37 | * Extended [options of KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/KeyframeEffect)
38 | */
39 | export interface TypedKeyframeEffectOptions
40 | extends Omit {
41 | easing?: TypedEasing;
42 | }
43 |
44 | /**
45 | * @internal
46 | */
47 | export const getKeyframeKeys = (keyframes: TypedKeyframe[]): string[] =>
48 | uniq(keyframes.flatMap(getKeys)).reduce((acc, k) => {
49 | if (["offset", "easing", "composite"].includes(k)) {
50 | // Ignore
51 | } else if (k === "cssFloat") {
52 | acc.push("float");
53 | } else if (k === "cssOffset") {
54 | acc.push("offset");
55 | } else {
56 | acc.push(k);
57 | }
58 | return acc;
59 | }, [] as string[]);
60 |
61 | /**
62 | * @internal
63 | */
64 | export const createAnimation = (
65 | el: Element | null,
66 | keyframes: Keyframe[] | null,
67 | options: KeyframeEffectOptions | undefined,
68 | timeline?: AnimationTimeline
69 | ): Animation => {
70 | const modifiedOptions: KeyframeEffectOptions = {
71 | fill: "both",
72 | ...options,
73 | };
74 | try {
75 | return new Animation(
76 | new KeyframeEffect(el, keyframes, modifiedOptions),
77 | timeline
78 | );
79 | } catch (e) {
80 | // Fallback to Element.animate()
81 | return el!.animate(keyframes, modifiedOptions);
82 | }
83 | };
84 |
85 | export type PlayOptions = {
86 | /**
87 | * If true, plays from the start. It's similar to GSAP's `restart()`.
88 | */
89 | restart?: boolean;
90 | };
91 |
92 | /**
93 | * @internal
94 | */
95 | export const _play = (animation: Animation, opts: PlayOptions = {}) => {
96 | // Reset reversed playback direction if completed
97 | if (animation.playbackRate < 0 && animation.playState === "finished") {
98 | _setRate(animation, (p) => -p);
99 | }
100 | if (opts.restart) {
101 | _setTime(animation, 0);
102 | }
103 | animation.play();
104 | };
105 |
106 | /**
107 | * @internal
108 | */
109 | export const _reverse = (animation: Animation | undefined) => {
110 | if (!animation) return;
111 | animation.reverse();
112 | };
113 |
114 | /**
115 | * @internal
116 | */
117 | export const _cancel = (animation: Animation | undefined) => {
118 | if (!animation) return;
119 | animation.cancel();
120 | };
121 |
122 | /**
123 | * @internal
124 | */
125 | export const _finish = (animation: Animation | undefined) => {
126 | if (!animation) return;
127 | animation.finish();
128 | };
129 |
130 | /**
131 | * @internal
132 | */
133 | export const _pause = (animation: Animation | undefined) => {
134 | if (!animation) return;
135 | animation.pause();
136 | };
137 | // /**
138 | // * @internal
139 | // */
140 | // export const _persist = (
141 | // animation: Animation | undefined,
142 | // el: Element,
143 | // keyframes: TypedKeyframe[]
144 | // ) => {
145 | // if (!animation) return;
146 | // // https://www.w3.org/TR/web-animations-1/#fill-behavior
147 | // if (animation.commitStyles) {
148 | // animation.commitStyles();
149 | // } else {
150 | // // Fallback for commitStyles
151 | // const computedStyle = getStyle(el);
152 | // getKeyframeKeys(keyframes).forEach((k) => {
153 | // ((el as HTMLElement).style as any)[k] = (computedStyle as any)[k];
154 | // });
155 | // }
156 | // animation.cancel();
157 | // };
158 | /**
159 | * @internal
160 | */
161 | export const _setTime = (
162 | animation: Animation | undefined,
163 | arg: number | ((endTime: number) => number)
164 | ) => {
165 | if (!animation) return;
166 | animation.currentTime =
167 | typeof arg === "function"
168 | ? arg(animation.effect!.getComputedTiming().endTime! as number)
169 | : arg;
170 | };
171 |
172 | /**
173 | * @internal
174 | */
175 | export const _setRate = (
176 | animation: Animation | undefined,
177 | arg: number | ((prevRate: number) => number)
178 | ) => {
179 | if (!animation) return;
180 | animation.updatePlaybackRate(
181 | typeof arg === "function" ? arg(animation.playbackRate) : arg
182 | );
183 | };
184 |
185 | export type WaitingAnimationEventName = "finish" | "reverseFinish";
186 |
187 | /**
188 | * @internal
189 | */
190 | export const _waitFor = (
191 | animation: Animation | undefined,
192 | name: WaitingAnimationEventName
193 | ): Promise => {
194 | if (!animation) return Promise.resolve();
195 |
196 | return new Promise((resolve) => {
197 | animation.onfinish = () => {
198 | if (
199 | (name === "finish" && animation.playbackRate > 0) ||
200 | (name === "reverseFinish" && animation.playbackRate < 0)
201 | ) {
202 | resolve();
203 | }
204 | };
205 | });
206 | };
207 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./core";
2 | export * from "./react";
3 |
--------------------------------------------------------------------------------
/src/react/components/TransitionGroup.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | createContext,
3 | Fragment,
4 | useRef,
5 | Children,
6 | useEffect,
7 | useState,
8 | ReactElement,
9 | useCallback,
10 | } from "react";
11 | import { noop } from "../../core/utils";
12 |
13 | const toMap = (elements: ReactElement[]) =>
14 | elements.reduce((acc, e, i) => {
15 | acc[e.key || i] = e;
16 | return acc;
17 | }, {} as { [key: string]: ReactElement });
18 |
19 | /** @internal */
20 | export type TransitionState = "update" | "enter" | "exit";
21 | /** @internal */
22 | export const TransitionStateContext = createContext("update");
23 |
24 | /** @internal */
25 | export const NOT_EXIT = 0;
26 | /** @internal */
27 | export const EXITING = 1;
28 | /** @internal */
29 | export const EXITED = 2;
30 | /** @internal */
31 | export type TransitionExitState =
32 | | typeof NOT_EXIT
33 | | typeof EXITING
34 | | typeof EXITED;
35 | /** @internal */
36 | export const TransitionNotifierContext =
37 | createContext<(state: TransitionExitState) => void>(noop);
38 |
39 | const Provider = ({
40 | _state: state,
41 | _element: element,
42 | }: {
43 | _state: TransitionState;
44 | _element: ReactElement;
45 | }): ReactElement => {
46 | const [show, setShow] = useState(true);
47 | const hasExitRef = useRef(false);
48 |
49 | const showChildren =
50 | state === "exit" ? (hasExitRef.current ? show : false) : true;
51 |
52 | useEffect(() => {
53 | if (state === "enter") {
54 | setShow(true);
55 | }
56 | }, [state]);
57 | return (
58 |
59 | {
61 | if (s === EXITED) {
62 | setShow(false);
63 | } else {
64 | hasExitRef.current = s === EXITING;
65 | }
66 | }, [])}
67 | >
68 | {showChildren ? element : null}
69 |
70 |
71 | );
72 | };
73 |
74 | export interface TransitionGroupProps {
75 | children: ReactElement | ReactElement[];
76 | }
77 |
78 | /**
79 | * A component to manage enter/update/exit of its children by key, that works similar to [TransitionGroup of react-transition-group](https://reactcommunity.org/react-transition-group/transition-group).
80 | */
81 | export const TransitionGroup = ({
82 | children,
83 | }: TransitionGroupProps): ReactElement => {
84 | const elemsRef = useRef(null!);
85 | const prevElems = elemsRef.current || [];
86 | const elems = Children.map(children, (c) => c);
87 |
88 | useEffect(() => {
89 | elemsRef.current = elems;
90 | });
91 |
92 | const elemsByKey = toMap(elems);
93 | const prevElemsByKey = toMap(prevElems);
94 |
95 | const res: ReactElement[] = [];
96 | prevElems.forEach((v, i) => {
97 | const key = v.key || i;
98 | if (elemsByKey[key]) {
99 | // update
100 | res.push(
101 |
102 | );
103 | } else {
104 | // exit
105 | res.push( );
106 | }
107 | });
108 | elems.forEach((v, i) => {
109 | const key = v.key || i;
110 | if (prevElemsByKey[key]) {
111 | // update
112 | } else {
113 | // enter
114 | res.push( );
115 | }
116 | });
117 |
118 | return {res} ;
119 | };
120 |
--------------------------------------------------------------------------------
/src/react/components/index.ts:
--------------------------------------------------------------------------------
1 | export { TransitionGroup } from "./TransitionGroup";
2 | export type { TransitionGroupProps } from "./TransitionGroup";
3 |
--------------------------------------------------------------------------------
/src/react/hooks/__snapshots__/useAnimation.ssr.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`SSR > should succeed with renderToStaticMarkup 1`] = `"test
"`;
4 |
5 | exports[`SSR > should succeed with renderToString 1`] = `"test
"`;
6 |
--------------------------------------------------------------------------------
/src/react/hooks/__snapshots__/useAnimationFunction.ssr.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`SSR > should succeed with renderToStaticMarkup 1`] = `"0
"`;
4 |
5 | exports[`SSR > should succeed with renderToString 1`] = `"0
"`;
6 |
--------------------------------------------------------------------------------
/src/react/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useAnimation";
2 | export * from "./useAnimationFunction";
3 | export * from "./useTransitionAnimation";
4 | export * from "./useScrollTimeline";
5 | export * from "./useViewTimeline";
6 |
--------------------------------------------------------------------------------
/src/react/hooks/state.ts:
--------------------------------------------------------------------------------
1 | import { isSameObject, isSameObjectArray } from "../../core/utils";
2 | import { createAnimation } from "../../core/waapi";
3 |
4 | /**
5 | * @internal
6 | */
7 | export interface AnimationObject {
8 | readonly _keyframes: Keyframe[];
9 | readonly _options: KeyframeEffectOptions | undefined;
10 | readonly _timeline: AnimationTimeline | undefined;
11 | }
12 |
13 | const animations = new WeakMap();
14 |
15 | /**
16 | * @internal
17 | */
18 | export const getAnimation = (target: AnimationObject) => animations.get(target);
19 |
20 | /**
21 | * @internal
22 | */
23 | export const deleteAnimation = (target: AnimationObject) => {
24 | getAnimation(target)?.cancel();
25 | animations.delete(target);
26 | };
27 |
28 | const isEqual = (a: AnimationObject, b: AnimationObject): boolean => {
29 | return (
30 | isSameObjectArray(a._keyframes, b._keyframes) &&
31 | isSameObject(a._options, b._options)
32 | );
33 | };
34 |
35 | /**
36 | * @internal
37 | */
38 | export const initAnimation = (
39 | el: Element,
40 | target: AnimationObject,
41 | prevTarget: AnimationObject | undefined
42 | ): Animation => {
43 | const prevAnimation = prevTarget && getAnimation(prevTarget);
44 |
45 | if (prevAnimation) {
46 | animations.delete(prevTarget);
47 |
48 | // Reuse animation if possible
49 | if (isEqual(target, prevTarget)) {
50 | animations.set(target, prevAnimation);
51 | return prevAnimation;
52 | }
53 | prevAnimation.cancel();
54 | }
55 |
56 | const animation = createAnimation(
57 | el,
58 | target._keyframes,
59 | target._options,
60 | target._timeline
61 | );
62 | animations.set(target, animation);
63 | return animation;
64 | };
65 |
--------------------------------------------------------------------------------
/src/react/hooks/useAnimation.ssr.spec.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @vitest-environment node
3 | */
4 | import { it, describe, expect } from "vitest";
5 | import { useEffect } from "react";
6 | import { renderToString, renderToStaticMarkup } from "react-dom/server";
7 | import { useAnimation } from "./useAnimation";
8 |
9 | describe("SSR", () => {
10 | const Comp = () => {
11 | const animate = useAnimation(
12 | { width: 100 },
13 | { duration: 600, easing: "ease-in-out" }
14 | );
15 | useEffect(() => {
16 | animate.play();
17 | }, []);
18 | return (
19 |
20 | test
21 |
22 | );
23 | };
24 |
25 | it("should succeed with renderToString", () => {
26 | expect(renderToString( )).toMatchSnapshot();
27 | });
28 |
29 | it("should succeed with renderToStaticMarkup", () => {
30 | expect(renderToStaticMarkup( )).toMatchSnapshot();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/react/hooks/useAnimation.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { assign, getStyle, toArray } from "../../core/utils";
3 | import {
4 | PlayOptions,
5 | _cancel,
6 | _waitFor,
7 | _finish,
8 | _pause,
9 | _play,
10 | _reverse,
11 | _setRate,
12 | _setTime,
13 | WaitingAnimationEventName,
14 | TypedKeyframe,
15 | GetKeyframeFunction,
16 | } from "../../core/waapi";
17 | import { useStatic } from "./useStatic";
18 | import { useLatestRef } from "./useLatestRef";
19 | import { AnimationDefinition } from "../types";
20 | import {
21 | deleteAnimation,
22 | getAnimation,
23 | AnimationObject,
24 | initAnimation,
25 | } from "./state";
26 |
27 | const normalizeKeyframe = (
28 | el: Element,
29 | keyframe: TypedKeyframe | TypedKeyframe[] | GetKeyframeFunction,
30 | args: Args
31 | ): TypedKeyframe[] => {
32 | if (typeof keyframe === "function") {
33 | return keyframe(getStyle(el), args);
34 | }
35 | return toArray(keyframe);
36 | };
37 |
38 | export type PlayOptionsWithArgs = PlayOptions & { args: Args };
39 |
40 | export interface BaseAnimationHandle {
41 | /**
42 | * A wrapper of Web Animations API's [play](https://developer.mozilla.org/en-US/docs/Web/API/Animation/play). It's similar to GSAP's `play()`.
43 | */
44 | play(
45 | ...opts: Args extends void ? [PlayOptions?] : [PlayOptionsWithArgs]
46 | ): BaseAnimationHandle;
47 | /**
48 | * A wrapper of Web Animations API's [reverse](https://developer.mozilla.org/en-US/docs/Web/API/Animation/reverse). It's similar to GSAP's `reverse()`.
49 | */
50 | reverse(): BaseAnimationHandle;
51 | /**
52 | * A wrapper of Web Animations API's [cancel](https://developer.mozilla.org/en-US/docs/Web/API/Animation/cancel). It's similar to GSAP's `kill()`.
53 | */
54 | cancel(): BaseAnimationHandle;
55 | /**
56 | * A wrapper of Web Animations API's [finish](https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish).
57 | */
58 | finish(): BaseAnimationHandle;
59 | /**
60 | * A wrapper of Web Animations API's [pause](https://developer.mozilla.org/en-US/docs/Web/API/Animation/pause). It's similar to GSAP's `pause()`.
61 | */
62 | pause(): BaseAnimationHandle;
63 | /**
64 | * A setter of Web Animations API's [currentTime](https://developer.mozilla.org/en-US/docs/Web/API/Animation/currentTime). It's similar to GSAP's `seek()`.
65 | *
66 | * If you pass function, you can get [endTime](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming#return_value) from its argument.
67 | */
68 | setTime(
69 | time: number | ((endTime: number) => number)
70 | ): BaseAnimationHandle;
71 | /**
72 | * A wrapper of Web Animations API's [updatePlaybackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/updatePlaybackRate). It's similar to GSAP's `timeScale()`.
73 | *
74 | * If you pass function, you can get current [playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate) from its argument.
75 | */
76 | setPlaybackRate(
77 | rate: number | ((prevRate: number) => number)
78 | ): BaseAnimationHandle;
79 | /**
80 | * A getter of Promise that will be resolved in specified timing.
81 | *
82 | * - `finished`: resolved when animation is finished and its playback direction is normal.
83 | * - `reverseFinished`: resolved when animation is finished and its playback direction is reversed.
84 | */
85 | waitFor(event: WaitingAnimationEventName): Promise>;
86 | }
87 |
88 | /**
89 | * Handle of {@link useAnimation}.
90 | * @typeParam Args - argument type
91 | */
92 | export interface AnimationHandle
93 | extends BaseAnimationHandle {
94 | /**
95 | * You have to pass this callback to ref of element you want to control.
96 | */
97 | (ref: Element | null): void;
98 | }
99 |
100 | /**
101 | * A basic hook to use Web Animations API. See {@link AnimationHandle}.
102 | * @typeParam Args - argument type
103 | */
104 | export const useAnimation = (
105 | ...args: AnimationDefinition
106 | ): AnimationHandle => {
107 | const argsRef = useLatestRef(args);
108 |
109 | const [handle, mount] = useStatic(
110 | (): [AnimationHandle, () => () => void] => {
111 | let _target: Element | null = null;
112 | let _active: AnimationObject | undefined;
113 |
114 | const init = (args: Args) => {
115 | if (!_target) return;
116 | const [keyframe, _options = {}] = argsRef.current;
117 | const { timeline, ...options } = _options;
118 |
119 | const prevActive = _active;
120 |
121 | return initAnimation(
122 | _target,
123 | (_active = {
124 | _keyframes: normalizeKeyframe(_target, keyframe, args),
125 | _options: options,
126 | _timeline: timeline?._get(),
127 | }),
128 | prevActive
129 | );
130 | };
131 | const clean = () => {
132 | if (_active) {
133 | deleteAnimation(_active);
134 | }
135 | };
136 |
137 | const externalHandle: AnimationHandle = assign(
138 | (ref: Element | null) => {
139 | if (!(_target = ref)) {
140 | clean();
141 | }
142 | },
143 | >{
144 | play: (...opts) => {
145 | const animation = init(((opts[0] || {}) as { args?: Args }).args!);
146 | if (animation) {
147 | _play(animation);
148 | }
149 | return externalHandle;
150 | },
151 | reverse: () => {
152 | if (_active) {
153 | _reverse(getAnimation(_active));
154 | }
155 | return externalHandle;
156 | },
157 | cancel: () => {
158 | if (_active) {
159 | _cancel(getAnimation(_active));
160 | }
161 | return externalHandle;
162 | },
163 | finish: () => {
164 | if (_active) {
165 | _finish(getAnimation(_active));
166 | }
167 | return externalHandle;
168 | },
169 | pause: () => {
170 | if (_active) {
171 | _pause(getAnimation(_active));
172 | }
173 | return externalHandle;
174 | },
175 | setTime: (time) => {
176 | let animation = _active && getAnimation(_active);
177 | if (!animation) {
178 | const [keyframe] = argsRef.current;
179 | if (typeof keyframe === "function") {
180 | return externalHandle;
181 | }
182 | // Init animation in setTime to start animation without calling play
183 | animation = init(undefined!);
184 | }
185 | _setTime(animation, time);
186 |
187 | return externalHandle;
188 | },
189 | setPlaybackRate: (rate) => {
190 | if (_active) {
191 | _setRate(getAnimation(_active), rate);
192 | }
193 | return externalHandle;
194 | },
195 | waitFor: (event) => {
196 | return _waitFor(_active && getAnimation(_active), event).then(
197 | () => externalHandle
198 | );
199 | },
200 | }
201 | );
202 |
203 | return [externalHandle, () => clean];
204 | }
205 | );
206 |
207 | useEffect(mount, []);
208 |
209 | return handle;
210 | };
211 |
--------------------------------------------------------------------------------
/src/react/hooks/useAnimationFunction.ssr.spec.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @vitest-environment node
3 | */
4 | import { it, describe, expect } from "vitest";
5 | import { useEffect, useState } from "react";
6 | import { renderToString, renderToStaticMarkup } from "react-dom/server";
7 | import {
8 | useAnimationFunction,
9 | ComputedTimingContext,
10 | } from "./useAnimationFunction";
11 |
12 | describe("SSR", () => {
13 | const Comp = () => {
14 | const [value, setValue] = useState(0);
15 | const animate = useAnimationFunction(
16 | (ctx: ComputedTimingContext) => {
17 | setValue(ctx.progress);
18 | },
19 | {
20 | duration: 600,
21 | easing: "ease-in-out",
22 | }
23 | );
24 | useEffect(() => {
25 | animate.play();
26 | }, []);
27 | return {value}
;
28 | };
29 |
30 | it("should succeed with renderToString", () => {
31 | expect(renderToString( )).toMatchSnapshot();
32 | });
33 |
34 | it("should succeed with renderToStaticMarkup", () => {
35 | expect(renderToStaticMarkup( )).toMatchSnapshot();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/react/hooks/useAnimationFunction.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { isSameObject } from "../../core/utils";
3 | import {
4 | TypedKeyframeEffectOptions,
5 | createAnimation,
6 | _cancel,
7 | _waitFor,
8 | _finish,
9 | _pause,
10 | _play,
11 | _reverse,
12 | _setRate,
13 | _setTime,
14 | WaitingAnimationEventName,
15 | } from "../../core/waapi";
16 | import type { BaseAnimationHandle } from "./useAnimation";
17 | import { useStatic } from "./useStatic";
18 | import { useLatestRef } from "./useLatestRef";
19 |
20 | /**
21 | * Handle of {@link useAnimationFunction}.
22 | * @typeParam Args - argument type
23 | */
24 | export interface AnimationFunctionHandle
25 | extends BaseAnimationHandle {}
26 |
27 | export interface AnimationFunctionOptions extends TypedKeyframeEffectOptions {}
28 |
29 | /**
30 | * Non nullable [ComputedEffectTiming](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getComputedTiming)
31 | */
32 | export type ComputedTimingContext = Required<{
33 | [key in keyof ComputedEffectTiming]: NonNullable;
34 | }>;
35 |
36 | /**
37 | * An argument of {@link useAnimationFunction}.
38 | * In this callback you can update any state or ref in JS.
39 | * - `ctx`: current animation state
40 | * - `args`: any argument passed from play
41 | */
42 | export type AnimationFunction = Args extends void
43 | ? (ctx: ComputedTimingContext) => void
44 | : (ctx: ComputedTimingContext, args: Args) => void;
45 |
46 | const bindUpdateFunction = (
47 | animation: Animation,
48 | getUpdateFunction: () => AnimationFunction,
49 | args: Args
50 | ) => {
51 | const update = () => {
52 | const timing = animation.effect?.getComputedTiming();
53 | if (!timing) return;
54 | const progress = timing.progress;
55 | if (progress != null) {
56 | getUpdateFunction()(timing as ComputedTimingContext, args);
57 | }
58 | if (animation.playState === "running") {
59 | requestAnimationFrame(update);
60 | }
61 | };
62 | animation.ready.then(update);
63 | };
64 |
65 | /**
66 | * Same as {@link useAnimation}, but it drives function not React element. See {@link AnimationFunctionHandle}.
67 | * @typeParam Args - argument type
68 | */
69 | export const useAnimationFunction = (
70 | onUpdate: AnimationFunction,
71 | options?: AnimationFunctionOptions
72 | ): AnimationFunctionHandle => {
73 | const onUpdateRef = useLatestRef(onUpdate);
74 | const optionsRef = useLatestRef(options);
75 |
76 | const [handle, cleanup] = useStatic(
77 | (): [AnimationFunctionHandle, () => void] => {
78 | const getOnUpdate = () => onUpdateRef.current;
79 |
80 | let cache: [Animation, AnimationFunctionOptions | undefined] | undefined;
81 | const initAnimation = (opts: { args?: Args } = {}): Animation => {
82 | const options = optionsRef.current;
83 | if (cache) {
84 | const [prevAnimation, prevOptions] = cache;
85 | // Reuse animation if possible
86 | if (isSameObject(options, prevOptions)) {
87 | if (prevAnimation.playState !== "running") {
88 | bindUpdateFunction(prevAnimation, getOnUpdate, opts.args!);
89 | }
90 | return prevAnimation;
91 | }
92 | prevAnimation.cancel();
93 | }
94 | const animation = createAnimation(null, null, options);
95 | bindUpdateFunction(animation, getOnUpdate, opts.args!);
96 | cache = [animation, options];
97 | return animation;
98 | };
99 | const getAnimation = () => cache?.[0];
100 |
101 | const externalHandle: AnimationFunctionHandle = {
102 | play: (...opts) => {
103 | _play(initAnimation(opts[0] as { args?: Args }), opts[0]);
104 | return externalHandle;
105 | },
106 | reverse: () => {
107 | _reverse(initAnimation());
108 | return externalHandle;
109 | },
110 | cancel: () => {
111 | _cancel(getAnimation());
112 | return externalHandle;
113 | },
114 | finish: () => {
115 | _finish(getAnimation());
116 | return externalHandle;
117 | },
118 | pause: () => {
119 | _pause(getAnimation());
120 | return externalHandle;
121 | },
122 | setTime: (time) => {
123 | _setTime(getAnimation(), time);
124 | return externalHandle;
125 | },
126 | setPlaybackRate: (rate) => {
127 | _setRate(getAnimation(), rate);
128 | return externalHandle;
129 | },
130 | waitFor: (event: WaitingAnimationEventName) =>
131 | _waitFor(getAnimation(), event).then(() => externalHandle),
132 | };
133 | return [externalHandle, externalHandle.cancel];
134 | }
135 | );
136 |
137 | useEffect(() => cleanup, []);
138 |
139 | return handle;
140 | };
141 |
--------------------------------------------------------------------------------
/src/react/hooks/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from "react";
2 |
3 | /**
4 | * https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
5 | * @internal
6 | */
7 | export const useIsomorphicLayoutEffect =
8 | typeof window !== "undefined" ? useLayoutEffect : useEffect;
9 |
--------------------------------------------------------------------------------
/src/react/hooks/useLatestRef.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from "react";
2 | import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
3 |
4 | /**
5 | * @internal
6 | */
7 | export const useLatestRef = (value: T) => {
8 | const ref = useRef(value);
9 |
10 | useIsomorphicLayoutEffect(() => {
11 | ref.current = value;
12 | }, [value]);
13 |
14 | return ref;
15 | };
16 |
--------------------------------------------------------------------------------
/src/react/hooks/useScrollTimeline.ts:
--------------------------------------------------------------------------------
1 | import { assign } from "../../core/utils";
2 | import { ScrollTimelineAxis, TimelineDefinition } from "../types";
3 | import { SCROLL_TIMELINE } from "../types/internal";
4 | import { useLatestRef } from "./useLatestRef";
5 | import { useStatic } from "./useStatic";
6 |
7 | export interface ScrollTimelineOpts {
8 | axis?: ScrollTimelineAxis;
9 | }
10 |
11 | export const useScrollTimeline = (
12 | opts: ScrollTimelineOpts = {}
13 | ): TimelineDefinition => {
14 | const optsRef = useLatestRef(opts);
15 |
16 | return useStatic(() => {
17 | let _target: Element | null;
18 | return assign(
19 | (ref: Element | null) => {
20 | _target = ref;
21 | },
22 | {
23 | _type: SCROLL_TIMELINE,
24 | _get: () => {
25 | const { axis } = optsRef.current;
26 | return new ScrollTimeline({
27 | source: _target || document.documentElement,
28 | axis,
29 | });
30 | },
31 | } as const
32 | );
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/src/react/hooks/useStatic.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from "react";
2 |
3 | /**
4 | * @internal
5 | */
6 | export const useStatic = (init: () => T): T => {
7 | const ref = useRef();
8 | return ref.current || (ref.current = init());
9 | };
10 |
--------------------------------------------------------------------------------
/src/react/hooks/useTransitionAnimation.ts:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect } from "react";
2 | import {
3 | EXITED,
4 | EXITING,
5 | NOT_EXIT,
6 | TransitionNotifierContext,
7 | TransitionState,
8 | TransitionStateContext,
9 | } from "../components/TransitionGroup";
10 | import { AnimationHandle, useAnimation } from "./useAnimation";
11 | import { getKeys, noop } from "../../core/utils";
12 | import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
13 | import type {
14 | TypedKeyframeEffectOptions,
15 | GetKeyframeFunction,
16 | TypedKeyframe,
17 | } from "../../core";
18 | import { useStatic } from "./useStatic";
19 | import { useLatestRef } from "./useLatestRef";
20 |
21 | export interface TransitionAnimationHandle {
22 | (ref: Element | null): void;
23 | }
24 |
25 | export interface TransitionAnimationOptions
26 | extends TypedKeyframeEffectOptions {}
27 |
28 | export type TransitionAnimationDefinition = [
29 | keyframe: TypedKeyframe | TypedKeyframe[] | GetKeyframeFunction,
30 | options?: TransitionAnimationOptions
31 | ];
32 |
33 | /**
34 | *
35 | * A hook to compose multiple {@link useAnimation} and plays them when element enter/update/exits.
36 | * This hook must be used under {@link TransitionGroup} component.
37 | */
38 | export const useTransitionAnimation = (keyframes: {
39 | enter?: TransitionAnimationDefinition;
40 | update?: TransitionAnimationDefinition;
41 | exit?: TransitionAnimationDefinition;
42 | }): TransitionAnimationHandle => {
43 | const keys = getKeys(keyframes);
44 | const animations = keys.reduce((acc, k) => {
45 | const def = keyframes[k];
46 | if (!def) return acc;
47 | acc[k] = useAnimation(def[0], def[1]);
48 | return acc;
49 | }, {} as { [key in TransitionState]: AnimationHandle | undefined });
50 |
51 | const animationsRef = useLatestRef(animations);
52 |
53 | const [animation, cleanup] = useStatic(
54 | (): [TransitionAnimationHandle, () => void] => {
55 | const forAllHandle = (fn: (handle: AnimationHandle) => void) => {
56 | getKeys(animationsRef.current).forEach((name) =>
57 | fn(animationsRef.current[name]!)
58 | );
59 | };
60 |
61 | const externalHandle: TransitionAnimationHandle = (
62 | ref: Element | null
63 | ) => {
64 | forAllHandle((h) => {
65 | h(ref);
66 | });
67 | };
68 | return [
69 | externalHandle,
70 | () => {
71 | forAllHandle((handle) => {
72 | handle.cancel();
73 | });
74 | },
75 | ];
76 | }
77 | );
78 |
79 | useEffect(() => cleanup, []);
80 |
81 | const currentState = useContext(TransitionStateContext);
82 | const notify = useContext(TransitionNotifierContext);
83 |
84 | useIsomorphicLayoutEffect(() => {
85 | // Decide if the parent should animate children on exit or not
86 | // State must change like enter (-> update) -> exit so it's ok to use ref
87 | if (keys.includes("exit")) {
88 | notify(EXITING);
89 | } else {
90 | notify(NOT_EXIT);
91 | }
92 | }, keys);
93 |
94 | useIsomorphicLayoutEffect(() => {
95 | if (currentState !== "update") return;
96 |
97 | animationsRef.current[currentState]?.play();
98 | });
99 |
100 | useIsomorphicLayoutEffect(() => {
101 | if (currentState === "update") return;
102 |
103 | animationsRef.current[currentState]
104 | ?.play()
105 | .waitFor("finish")
106 | .then(() => {
107 | if (currentState === "exit") {
108 | notify(EXITED);
109 | }
110 | })
111 | .catch(
112 | noop // ignore uncaught promise error
113 | );
114 | }, [currentState]);
115 |
116 | return animation;
117 | };
118 |
--------------------------------------------------------------------------------
/src/react/hooks/useViewTimeline.ts:
--------------------------------------------------------------------------------
1 | import { assign } from "../../core/utils";
2 | import {
3 | ScrollTimelineAxis,
4 | TimelineDefinition,
5 | ViewTimelineInset,
6 | } from "../types";
7 | import { VIEW_TIMELINE } from "../types/internal";
8 | import { useLatestRef } from "./useLatestRef";
9 | import { useStatic } from "./useStatic";
10 |
11 | export interface ViewTimelineOpts {
12 | axis?: ScrollTimelineAxis;
13 | inset?: ViewTimelineInset;
14 | }
15 |
16 | export const useViewTimeline = (
17 | opts: ViewTimelineOpts = {}
18 | ): TimelineDefinition => {
19 | const optsRef = useLatestRef(opts);
20 |
21 | return useStatic(() => {
22 | let _target: Element | null;
23 | return assign(
24 | (ref: Element | null) => {
25 | _target = ref;
26 | },
27 | {
28 | _type: VIEW_TIMELINE,
29 | _get: () => {
30 | const { axis, inset } = optsRef.current;
31 | return new ViewTimeline({
32 | subject: _target || document.documentElement,
33 | axis,
34 | inset,
35 | });
36 | },
37 | } as const
38 | );
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/src/react/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components";
2 | export * from "./hooks";
3 | export * from "./types";
4 |
--------------------------------------------------------------------------------
/src/react/types/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GetKeyframeFunction,
3 | TypedKeyframe,
4 | TypedKeyframeEffectOptions,
5 | } from "../../core";
6 | import { SCROLL_TIMELINE, VIEW_TIMELINE } from "./internal";
7 |
8 | export interface AnimationOptions extends TypedKeyframeEffectOptions {
9 | timeline?: TimelineDefinition;
10 | }
11 |
12 | export type AnimationDefinition = [
13 | keyframe: TypedKeyframe | TypedKeyframe[] | GetKeyframeFunction,
14 | options?: AnimationOptions
15 | ];
16 |
17 | export interface TimelineDefinition {
18 | (el: Element | null): void;
19 | /**
20 | * @internal
21 | */
22 | _type: typeof SCROLL_TIMELINE | typeof VIEW_TIMELINE;
23 | /**
24 | * @internal
25 | */
26 | _get: () => AnimationTimeline;
27 | }
28 |
29 | export type ScrollTimelineAxis = "block" | "inline" | "y" | "x";
30 |
31 | export type ViewTimelineInset = "auto" | (string & {});
32 |
--------------------------------------------------------------------------------
/src/react/types/internal.ts:
--------------------------------------------------------------------------------
1 | /** @internal */
2 | export const SCROLL_TIMELINE = 1;
3 | /** @internal */
4 | export const VIEW_TIMELINE = 2;
5 |
--------------------------------------------------------------------------------
/stories/hooks/useAnimation.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useCallback, useEffect, useState } from "react";
3 | import {
4 | AnimationOptions,
5 | TypedEasing,
6 | TypedKeyframe,
7 | useAnimation,
8 | } from "../../src";
9 | import { mergeRefs } from "react-merge-refs";
10 |
11 | const debounce = void>(fn: T, ms: number) => {
12 | let id: NodeJS.Timeout | null = null;
13 | return (...args: Parameters) => {
14 | if (id != null) {
15 | clearTimeout(id);
16 | }
17 | id = setTimeout(() => {
18 | id = null;
19 | fn(...args);
20 | }, ms);
21 | };
22 | };
23 |
24 | export default { component: useAnimation };
25 |
26 | export const Playground: StoryObj = {
27 | render: () => {
28 | const [duration, setDuration] = useState(1000);
29 | const [iteration, setIteration] = useState(2);
30 | const [direction, setDirection] = useState("alternate");
31 | const [easing, setEasing] = useState("cubic-bezier");
32 | const [cubicBezierValues, setCubicBezierValues] = useState<
33 | [number, number, number, number]
34 | >([0.65, 0, 0.35, 1]);
35 | const [stepsValues, setStepsValues] = useState<[number, string]>([
36 | 20,
37 | "end",
38 | ]);
39 | const [delay, setDelay] = useState(0);
40 | const [endDelay, setEndDelay] = useState(0);
41 |
42 | const animate = useAnimation(
43 | [
44 | { transform: "translateX(0px)" },
45 | {
46 | transform: "translateX(100px) scale(2.0)",
47 | fill: "red",
48 | },
49 | ],
50 | {
51 | duration: duration,
52 | easing:
53 | easing === "cubic-bezier"
54 | ? `cubic-bezier(${cubicBezierValues.join(",")})`
55 | : easing === "steps"
56 | ? `steps(${stepsValues.join(",")})`
57 | : easing,
58 | direction: direction,
59 | iterations: iteration,
60 | delay,
61 | endDelay,
62 | }
63 | );
64 |
65 | useEffect(() => {
66 | animate.play();
67 | }, [
68 | duration,
69 | easing,
70 | cubicBezierValues,
71 | stepsValues,
72 | iteration,
73 | direction,
74 | delay,
75 | ]);
76 |
77 | return (
78 |
79 |
80 |
81 |
82 | Hello world
83 |
84 |
85 |
86 |
87 | animate.play()}>play
88 | pause
89 | reverse
90 | finish
91 |
92 |
93 |
94 | duration:
95 | {
100 | setDuration(Number(e.target.value));
101 | }}
102 | />
103 | ms
104 |
105 |
106 |
107 |
108 | iteration:
109 | {
114 | setIteration(Number(e.target.value));
115 | }}
116 | />
117 |
118 |
119 |
120 | easing:
121 | {["linear", "ease", "ease-in", "ease-out", "ease-in-out"].map((v) => (
122 |
123 | {
128 | setEasing(e.target.value as TypedEasing);
129 | }}
130 | />
131 | {v}
132 |
133 | ))}
134 |
135 | {
140 | setEasing(e.target.value as TypedEasing);
141 | }}
142 | />
143 | cubic-bezier(
144 | {cubicBezierValues
145 | .map((v, i) => (
146 | {
154 | setEasing("cubic-bezier");
155 | setCubicBezierValues((prev) => {
156 | const next: [number, number, number, number] = [...prev];
157 | next[i] = Number(e.target.value);
158 | return next;
159 | });
160 | }}
161 | />
162 | ))
163 | .reduce((acc, v, i) => {
164 | acc.push(v);
165 | if (i !== cubicBezierValues.length - 1) {
166 | acc.push(",");
167 | }
168 | return acc;
169 | }, [] as React.ReactNode[])}
170 | )
171 |
172 |
173 | {
178 | setEasing(e.target.value as TypedEasing);
179 | }}
180 | />
181 | steps(
182 | {
183 | {
189 | setEasing("steps");
190 | setStepsValues((prev) => [Number(e.target.value), prev[1]]);
191 | }}
192 | />
193 | }
194 | ,
195 | {
198 | setEasing("steps");
199 | setStepsValues((prev) => [prev[0], e.target.value]);
200 | }}
201 | >
202 | {[
203 | "jump-start",
204 | "jump-end",
205 | "jump-none",
206 | "jump-both",
207 | "start",
208 | "end",
209 | ].map((v) => (
210 |
211 | {v}
212 |
213 | ))}
214 |
215 | )
216 |
217 |
218 |
219 | direction:
220 | {["normal", "reverse", "alternate", "alternate-reverse"].map((v) => (
221 |
222 | {
227 | setDirection(e.target.value as PlaybackDirection);
228 | }}
229 | />
230 | {v}
231 |
232 | ))}
233 |
234 |
235 |
236 | delay:
237 | {
242 | setDelay(Number(e.target.value));
243 | }}
244 | />
245 | ms
246 |
247 |
248 |
249 |
250 | endDelay:
251 | {
256 | setEndDelay(Number(e.target.value));
257 | }}
258 | />
259 | ms
260 |
261 |
262 |
263 |
264 | time:
265 | {
271 | animate.setTime((end) => end * (Number(e.target.value) / 100));
272 | }}
273 | />
274 |
275 |
276 |
277 |
278 | playback rate:
279 | {
285 | animate.setPlaybackRate(Number(e.target.value));
286 | }}
287 | />
288 |
289 |
290 |
291 | );
292 | },
293 | };
294 |
295 | const Bar = ({
296 | value,
297 | i,
298 | height,
299 | keep,
300 | }: {
301 | value: number;
302 | i: number;
303 | height: number;
304 | keep: boolean;
305 | }) => {
306 | const target: TypedKeyframe = {
307 | height: `${value}px`,
308 | transform: `translateY(-${value}px)`,
309 | opacity: String(1 - i * 0.025),
310 | };
311 | const animate = useAnimation(
312 | keep
313 | ? (prev) => [
314 | {
315 | height: prev.height,
316 | transform: prev.transform,
317 | opacity: prev.opacity,
318 | },
319 | target,
320 | ]
321 | : [target],
322 | { duration: 150, easing: "ease-out", delay: i * 100 }
323 | );
324 |
325 | useEffect(() => {
326 | animate.play();
327 | }, [value, keep]);
328 |
329 | return (
330 |
339 | );
340 | };
341 |
342 | export const Bars: StoryObj = {
343 | render: () => {
344 | const init = () =>
345 | Array.from({ length: 30 }).map(() => 300 * Math.random() ** 2);
346 | const [rects, setRects] = useState(init);
347 | const [keep, setKeep] = useState(false);
348 |
349 | const width = 800;
350 | const height = 400;
351 | const margin = 10;
352 | const maxBarHeight = height - margin * 2;
353 |
354 | const refresh = () => setRects(init());
355 |
356 | return (
357 | <>
358 |
359 | refresh
360 |
361 | {
365 | setKeep(e.target.checked);
366 | refresh();
367 | }}
368 | />
369 | keep
370 |
371 |
372 |
373 |
374 |
375 | {rects.map((v, i) => (
376 |
383 | ))}
384 |
385 |
386 |
387 | >
388 | );
389 | },
390 | };
391 |
392 | export const Mouse: StoryObj = {
393 | render: () => {
394 | const rotate = useAnimation(
395 | [
396 | { transform: "rotate(0deg)", borderRadius: "1rem" },
397 | { transform: "rotate(360deg)", borderRadius: "50%" },
398 | { transform: "rotate(720deg)", borderRadius: "1rem" },
399 | ],
400 | {
401 | duration: 1000,
402 | iterations: Infinity,
403 | easing: "ease-in-out",
404 | }
405 | );
406 | const move = useAnimation<{ x: number; y: number }>(
407 | (prev, pos) => [
408 | { transform: prev.transform },
409 | { transform: `translate(${pos.x}px, ${pos.y}px)` },
410 | ],
411 | {
412 | duration: 400,
413 | easing: "ease-in-out",
414 | }
415 | );
416 |
417 | useEffect(() => {
418 | rotate.play();
419 |
420 | const onPointerMove = debounce((e: PointerEvent) => {
421 | move.play({ args: { x: e.clientX, y: e.clientY } });
422 | }, 100);
423 | window.addEventListener("pointermove", onPointerMove);
424 | return () => {
425 | window.removeEventListener("pointermove", onPointerMove);
426 | };
427 | }, []);
428 |
429 | return (
430 |
431 |
Move mouse cursor.
432 |
445 |
446 | );
447 | },
448 | };
449 |
450 | export const Toggle: StoryObj = {
451 | render: () => {
452 | const animate = useAnimation(
453 | { transform: "translate3d(400px, 0, 0)" },
454 | { duration: 800, easing: "ease-in-out" }
455 | );
456 |
457 | useEffect(() => {
458 | animate.play();
459 | }, [animate]);
460 |
461 | return (
462 |
463 |
Toggle
464 |
467 |
486 |
487 | );
488 | },
489 | };
490 |
491 | export const Path: StoryObj = {
492 | render: () => {
493 | const animate = useAnimation(
494 | [
495 | { d: "path('M50,50L150,50L150,150L50,150z')" },
496 | { d: "path('M100,30L170,170L30,170L30,170z')" },
497 | ],
498 | {
499 | duration: 1000,
500 | easing: "ease-in-out",
501 | iterations: Infinity,
502 | direction: "alternate",
503 | }
504 | );
505 |
506 | useEffect(() => {
507 | animate.play();
508 | }, []);
509 |
510 | const width = 400;
511 | const height = 300;
512 |
513 | return (
514 |
515 |
516 |
517 | );
518 | },
519 | };
520 |
521 | export const ThreeDimentional: StoryObj = {
522 | render: () => {
523 | const [pos, setPos] = useState({ x: 1, y: 1 });
524 | const animate = useAnimation(
525 | { transform: `rotate3d(${pos.y / 100}, ${pos.x / 100}, 0, 360deg)` },
526 | {
527 | duration: 1000,
528 | iterations: Infinity,
529 | }
530 | );
531 |
532 | useEffect(() => {
533 | animate.play();
534 | }, [pos]);
535 |
536 | useEffect(() => {
537 | const onPointerMove = (e: PointerEvent) => {
538 | setPos({ x: e.clientX, y: e.clientY });
539 | };
540 | window.addEventListener("pointermove", onPointerMove);
541 | return () => {
542 | window.removeEventListener("pointermove", onPointerMove);
543 | };
544 | }, []);
545 |
546 | return (
547 | <>
548 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
603 | >
604 | );
605 | },
606 | name: "3D",
607 | };
608 |
609 | export const Sequence: StoryObj = {
610 | render: () => {
611 | const animate = useAnimation(
612 | (prev, color) => [{ fill: prev.fill }, { fill: color }],
613 | { duration: 600, easing: "ease-out" }
614 | );
615 |
616 | const onClickAll = useCallback(async () => {
617 | try {
618 | await animate.play({ args: "red" }).waitFor("finish");
619 | await animate.play({ args: "blue" }).waitFor("finish");
620 | await animate.play({ args: "green" }).waitFor("finish");
621 | } catch (e) {
622 | // ignore uncaught promise error
623 | }
624 | }, []);
625 |
626 | useEffect(() => {
627 | onClickAll();
628 | }, []);
629 |
630 | return (
631 |
632 |
633 |
637 |
638 |
639 | {
641 | animate.play({ args: "red" });
642 | }}
643 | >
644 | Red
645 |
646 | {
648 | animate.play({ args: "blue" });
649 | }}
650 | >
651 | Blue
652 |
653 | {
655 | animate.play({ args: "green" });
656 | }}
657 | >
658 | Green
659 |
660 | All
661 |
662 |
663 | );
664 | },
665 | };
666 |
667 | const WavedRect = ({ i }: { i: number }) => {
668 | const baseTiming: AnimationOptions = {
669 | easing: "ease-in-out",
670 | iterations: Infinity,
671 | direction: "alternate",
672 | delay: i * 98,
673 | };
674 | const move = useAnimation(
675 | [
676 | { transform: "translateY(0) scaleX(.8)" },
677 | { transform: "translateY(95vh) scaleX(1)" },
678 | ],
679 | { ...baseTiming, duration: 2500 }
680 | );
681 | const opacity = useAnimation([{ opacity: 1 }, { opacity: 0 }], {
682 | ...baseTiming,
683 | duration: 2000,
684 | });
685 | const color = useAnimation(
686 | [{ backgroundColor: "rgb(239, 239, 255)" }, { backgroundColor: "#e4c349" }],
687 | { ...baseTiming, duration: 3000 }
688 | );
689 |
690 | useEffect(() => {
691 | move.play();
692 | opacity.play();
693 | color.play();
694 | }, []);
695 |
696 | return (
697 |
706 | );
707 | };
708 |
709 | export const Wave: StoryObj = {
710 | render: () => {
711 | const [rects] = useState(() => Array.from({ length: 20 }).map((_, i) => i));
712 |
713 | return (
714 |
723 | {rects.map((i) => (
724 |
725 | ))}
726 |
727 | );
728 | },
729 | };
730 |
731 | export const Countdown: StoryObj = {
732 | render: () => {
733 | const [count, setCount] = useState(10);
734 | const countAnimation = useAnimation(
735 | [
736 | { opacity: 1, transform: "scale(.6)" },
737 | { opacity: 0.5, transform: "scale(1)" },
738 | ],
739 | {
740 | duration: 500,
741 | easing: "linear",
742 | delay: 0,
743 | iterations: 1000,
744 | direction: "alternate",
745 | }
746 | );
747 | const boomAnimation = useAnimation(
748 | [
749 | {
750 | opacity: 0,
751 | transform: "scale(.01) rotate(0deg)",
752 | color: "white",
753 | offset: 0,
754 | },
755 | {
756 | opacity: 1,
757 | transform: "scale(6) rotate(360deg)",
758 | color: "orange",
759 | offset: 0.8,
760 | },
761 | {
762 | opacity: 1,
763 | transform: "scale(1) rotate(720deg)",
764 | color: "white",
765 | offset: 1,
766 | },
767 | ],
768 | {
769 | duration: 2000,
770 | easing: "ease-out",
771 | delay: 0,
772 | iterations: 1,
773 | }
774 | );
775 |
776 | useEffect(() => {
777 | countAnimation.play();
778 | let startCount = count;
779 |
780 | const id = setInterval(() => {
781 | startCount -= 1;
782 | setCount((p) => p - 1);
783 |
784 | if (startCount > 0) {
785 | countAnimation.setPlaybackRate((prev) => Math.min(prev * 1.15, 6));
786 | } else {
787 | clearInterval(id);
788 | boomAnimation.play();
789 | }
790 | }, 1000);
791 |
792 | return () => {
793 | clearInterval(id);
794 | };
795 | }, []);
796 | return (
797 |
807 |
811 | {count}
812 |
813 |
814 | );
815 | },
816 | };
817 |
818 | // const Block = ({ i, length: n }: { i: number; length: number }) => {
819 | // const timing: AnimationOptions = {
820 | // duration: 250,
821 | // };
822 | // const one = useAnimation(
823 | // [{ backgroundColor: "#eee" }, { backgroundColor: "steelblue" }],
824 | // { ...timing, endDelay: 1000 }
825 | // );
826 | // const two = useAnimation(
827 | // [{ backgroundColor: "steelblue" }, { backgroundColor: "orange" }],
828 | // { ...timing, endDelay: 1000 }
829 | // );
830 | // const three = useAnimation(
831 | // [{ backgroundColor: "orange" }, { backgroundColor: "#eee" }],
832 | // { ...timing, endDelay: n }
833 | // );
834 |
835 | // useEffect(() => {
836 | // one.cancel();
837 | // two.cancel();
838 | // three.cancel();
839 | // const run = async () => {
840 | // try {
841 | // await one.play().waitFor("finish");
842 | // one.cancel();
843 | // await two.play().waitFor("finish");
844 | // two.cancel();
845 | // await three.play().waitFor("finish");
846 | // three.cancel();
847 | // run();
848 | // } catch (e) {
849 | // // ignore uncaught promise error
850 | // }
851 | // };
852 | // setTimeout(run, i + (Math.random() * n) / 4);
853 | // }, []);
854 |
855 | // return (
856 | //
867 | // );
868 | // };
869 |
870 | // export const Chained: StoryObj = {
871 | // render: () => {
872 | // const length = 4002;
873 | // return (
874 | //
875 | // {Array.from({ length: length }).map((_, i) => (
876 | //
877 | // ))}
878 | //
879 | // );
880 | // },
881 | // };
882 |
--------------------------------------------------------------------------------
/stories/hooks/useAnimationFunction.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useCallback, useEffect, useRef, useState } from "react";
3 | import { useAnimationFunction } from "../../src";
4 |
5 | export default { component: useAnimationFunction };
6 |
7 | export const Text: StoryObj = {
8 | render: () => {
9 | const [enable, setEnable] = useState(true);
10 | const [time, setTime] = useState(0);
11 | const animate = useAnimationFunction(
12 | ({ progress }) => {
13 | setTime(progress);
14 | },
15 | {
16 | duration: 1000,
17 | easing: "ease-in-out",
18 | iterations: Infinity,
19 | direction: "alternate",
20 | }
21 | );
22 |
23 | useEffect(() => {
24 | animate.play();
25 | }, [animate]);
26 |
27 | const onClick = useCallback(() => {
28 | setEnable((p) => !p);
29 | if (enable) {
30 | animate.pause();
31 | } else {
32 | animate.play();
33 | }
34 | }, [animate, enable]);
35 |
36 | return (
37 |
38 |
{enable ? "stop" : "start"}
39 |
{time.toFixed(6)}
40 |
41 | );
42 | },
43 | };
44 |
45 | export const Progress: StoryObj = {
46 | render: () => {
47 | const [value, setValue] = useState(0);
48 | const animate = useAnimationFunction<{ start: number; end: number }>(
49 | ({ progress }, arg) => {
50 | setValue(arg.start * (1 - progress) + progress * arg.end);
51 | },
52 | {
53 | duration: 600,
54 | easing: "ease-in-out",
55 | }
56 | );
57 | useEffect(() => {
58 | animate.play({ args: { start: value, end: Math.random() * 100 } });
59 | }, []);
60 |
61 | return (
62 | <>
63 |
66 |
67 | animate.play({ args: { start: value, end: 0 } })}
69 | >
70 | 0%
71 |
72 | animate.play({ args: { start: value, end: 50 } })}
74 | >
75 | 50%
76 |
77 | animate.play({ args: { start: value, end: 100 } })}
79 | >
80 | 100%
81 |
82 |
83 | >
84 | );
85 | },
86 | };
87 |
88 | export const Canvas: StoryObj = {
89 | render: () => {
90 | const ref = useRef(null);
91 | const ctxRef = useRef(null);
92 | const width = 400;
93 | const height = 400;
94 | const animate = useAnimationFunction(
95 | (timing) => {
96 | if (!ref.current) return;
97 | const ctx =
98 | ctxRef.current || (ctxRef.current = ref.current.getContext("2d")!);
99 | ctx.clearRect(0, 0, width, height);
100 | const fillStyle = ["red", "blue", "green"][timing.currentIteration % 3];
101 | ctx.fillStyle = fillStyle;
102 | ctx.font = ctx.font.replace(
103 | /\d+px/,
104 | `${Math.floor(timing.progress * 50)}px`
105 | );
106 | ctx.fillText(fillStyle, 40, 80);
107 | ctx.beginPath();
108 | ctx.arc(width / 2, height / 2, 50 * timing.progress, 0, Math.PI * 2);
109 | ctx.fill();
110 | },
111 | {
112 | easing: "ease-in-out",
113 | duration: 1000,
114 | iterations: Infinity,
115 | }
116 | );
117 |
118 | useEffect(() => {
119 | animate.play();
120 | }, [animate]);
121 |
122 | return ;
123 | },
124 | };
125 |
--------------------------------------------------------------------------------
/stories/hooks/useScrollTimeline.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation, useScrollTimeline } from "../../src";
4 |
5 | export default { component: useScrollTimeline };
6 |
7 | export const Document: StoryObj = {
8 | render: () => {
9 | const size = 100;
10 | const height = 2000;
11 |
12 | const animate = useAnimation(
13 | [{ transform: `translate(0px, ${height - size}px) rotate(3600deg)` }],
14 | {
15 | duration: 500,
16 | easing: "ease-in",
17 | timeline: useScrollTimeline(),
18 | }
19 | );
20 | useEffect(() => {
21 | animate.play();
22 | }, []);
23 |
24 | return (
25 |
31 | Please scroll down!
32 |
43 |
44 | );
45 | },
46 | };
47 |
48 | export const Overflow: StoryObj = {
49 | render: () => {
50 | const size = 100;
51 | const height = 2000;
52 |
53 | const timeline = useScrollTimeline();
54 | const animate = useAnimation(
55 | [{ transform: `translate(0px, ${height - size}px) rotate(3600deg)` }],
56 | {
57 | duration: 500,
58 | easing: "ease-in",
59 | timeline,
60 | }
61 | );
62 | useEffect(() => {
63 | animate.play();
64 | }, []);
65 |
66 | return (
67 |
73 |
82 |
88 | Please scroll down!
89 |
100 |
101 |
102 |
103 | );
104 | },
105 | };
106 |
--------------------------------------------------------------------------------
/stories/hooks/useTransitionAnimation.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect, useRef, useState } from "react";
3 | import {
4 | TransitionGroup,
5 | AnimationOptions,
6 | useTransitionAnimation,
7 | } from "../../src";
8 |
9 | export default { component: useTransitionAnimation };
10 |
11 | const usePrevious = (value: T) => {
12 | const prev = useRef(value);
13 | useEffect(() => {
14 | prev.current = value;
15 | }, [value]);
16 | return prev.current;
17 | };
18 |
19 | const Text = ({ children }: { children: string }) => {
20 | const prev = usePrevious(children);
21 |
22 | const timing = { duration: 800, easing: "ease-out" };
23 | const animate = useTransitionAnimation({
24 | enter: [
25 | [
26 | { transform: "translateY(-20px)", opacity: 0.2 },
27 | { transform: "translateY(0px)", opacity: 1 },
28 | ],
29 | timing,
30 | ],
31 | exit: [
32 | [
33 | { transform: "translateY(0px)", opacity: 1 },
34 | { transform: "translateY(20px)", opacity: 0.2 },
35 | ],
36 | timing,
37 | ],
38 | update: [
39 | [{ transform: "rotateX(360deg)" }, { transform: "rotateX(0deg)" }],
40 | children !== prev ? timing : undefined,
41 | ],
42 | });
43 |
44 | return (
45 |
49 | {children}
50 |
51 | );
52 | };
53 |
54 | export const Input: StoryObj = {
55 | render: () => {
56 | const [value, setValue] = useState("Animation");
57 | return (
58 |
59 |
60 | setValue(e.target.value)} />
61 |
62 |
63 |
64 | {value.split("").map((t, i) => (
65 | {t}
66 | ))}
67 |
68 |
69 |
70 | );
71 | },
72 | };
73 |
74 | const shuffle = (array: T[]): T[] => {
75 | for (let i = array.length - 1; i >= 0; i--) {
76 | const j = Math.floor(Math.random() * (i + 1));
77 | [array[i], array[j]] = [array[j], array[i]];
78 | }
79 | return array;
80 | };
81 |
82 | const SvgText = ({ children, i }: { children: string; i: number }) => {
83 | const x = i * 20;
84 | const prevX = usePrevious(x);
85 | const timing = { duration: 800, easing: "ease-in-out" };
86 | const transition = useTransitionAnimation({
87 | update: [
88 | (prev) => [
89 | { fill: prev.fill, transform: `translateX(${prevX - x}px)` },
90 | { fill: "#333", transform: `translateX(0px)` },
91 | ],
92 | timing,
93 | ],
94 | enter: [
95 | [
96 | { fill: "green", fillOpacity: "0", transform: "translateY(-20px)" },
97 | { fill: "green", fillOpacity: "1", transform: "translateY(0px)" },
98 | ],
99 | timing,
100 | ],
101 | exit: [
102 | { fill: "brown", fillOpacity: "0", transform: "translateY(20px)" },
103 | timing,
104 | ],
105 | });
106 |
107 | return (
108 |
109 | {children}
110 |
111 | );
112 | };
113 |
114 | const ALPHABETS = "abcdefghijklmnopqrstuvwxyz".split("");
115 |
116 | export const Alphabet: StoryObj = {
117 | render: () => {
118 | const [texts, setTexts] = useState(ALPHABETS);
119 | useEffect(() => {
120 | const id = setInterval(() => {
121 | const shuffled = shuffle(ALPHABETS)
122 | .slice(0, Math.floor(Math.random() * 26))
123 | .sort();
124 | setTexts(shuffled);
125 | }, 1000);
126 | return () => {
127 | clearInterval(id);
128 | };
129 | }, []);
130 |
131 | return (
132 | <>
133 |
134 |
135 |
136 | {texts.map((t, i) => (
137 |
138 | {t}
139 |
140 | ))}
141 |
142 |
143 |
144 |
145 | >
146 | );
147 | },
148 | };
149 |
150 | const ExpandRect = ({ i, length }: { i: number; length: number }) => {
151 | const timing: AnimationOptions = {
152 | easing: "ease-in-out",
153 | direction: "alternate",
154 | duration: 1000,
155 | };
156 |
157 | const startStyle = (s: CSSStyleDeclaration, defaultScale: number) => {
158 | const transform = s.transform;
159 | const scale =
160 | transform.slice("matrix".length + 1, transform.indexOf(",") - 1) ||
161 | defaultScale;
162 | return {
163 | backgroundColor: s.backgroundColor,
164 | transform: `scale(${scale})`,
165 | opacity: s.opacity,
166 | };
167 | };
168 |
169 | const transition = useTransitionAnimation({
170 | enter: [
171 | (prev) => [
172 | startStyle(prev, 0),
173 | { backgroundColor: "skyblue", transform: "scale(1)", opacity: 1 },
174 | ],
175 | { ...timing, delay: i * 100 },
176 | ],
177 | exit: [
178 | (prev) => [
179 | startStyle(prev, 1),
180 | {
181 | backgroundColor: "limegreen",
182 | transform: "scale(0)",
183 | opacity: 0,
184 | },
185 | ],
186 | { ...timing, delay: (length - i) * 100 },
187 | ],
188 | });
189 |
190 | return (
191 |
203 | );
204 | };
205 |
206 | export const Expand: StoryObj = {
207 | render: () => {
208 | const [expanded, setExpanded] = useState(true);
209 | const length = 16;
210 | const [rects] = useState(() => Array.from({ length }).map((_, i) => i));
211 |
212 | return (
213 | <>
214 | setExpanded((prev) => !prev)}
216 | style={{ marginBottom: 10 }}
217 | >
218 | {expanded ? "close" : "open"}
219 |
220 |
232 |
233 | {expanded
234 | ? rects.map((i) => )
235 | : []}
236 |
237 |
238 | >
239 | );
240 | },
241 | };
242 |
--------------------------------------------------------------------------------
/stories/hooks/useViewTimeline.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation, useViewTimeline } from "../../src";
4 |
5 | export default { component: useViewTimeline };
6 |
7 | export const Document: StoryObj = {
8 | render: () => {
9 | const animate = useAnimation(
10 | [
11 | { opacity: 0, transform: "scaleX(0)" },
12 | { opacity: 1, transform: "scaleX(1)" },
13 | ],
14 | {
15 | duration: 200,
16 | timeline: useViewTimeline(),
17 | }
18 | );
19 | useEffect(() => {
20 | animate.play();
21 | }, []);
22 |
23 | return (
24 |
25 |
Content
26 |
27 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
28 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Risus quis
29 | varius quam quisque id. Et ligula ullamcorper malesuada proin libero
30 | nunc consequat interdum varius. Elit ullamcorper dignissim cras
31 | tincidunt lobortis feugiat vivamus at augue.
32 |
33 |
34 | Dolor sed viverra ipsum nunc aliquet. Sed sed risus pretium quam
35 | vulputate dignissim. Tortor aliquam nulla facilisi cras. A erat nam at
36 | lectus urna duis convallis convallis. Nibh ipsum consequat nisl vel
37 | pretium lectus. Sagittis aliquam malesuada bibendum arcu vitae
38 | elementum. Malesuada bibendum arcu vitae elementum curabitur vitae
39 | nunc sed velit.
40 |
41 |
50 |
51 | Adipiscing enim eu turpis egestas pretium aenean pharetra magna ac.
52 | Arcu cursus vitae congue mauris rhoncus aenean vel. Sit amet cursus
53 | sit amet dictum. Augue neque gravida in fermentum et. Gravida rutrum
54 | quisque non tellus orci ac auctor augue mauris. Risus quis varius quam
55 | quisque id diam vel quam elementum. Nibh praesent tristique magna sit
56 | amet purus gravida quis. Duis ultricies lacus sed turpis tincidunt id
57 | aliquet. In egestas erat imperdiet sed euismod nisi. Eget egestas
58 | purus viverra accumsan in nisl nisi scelerisque. Netus et malesuada
59 | fames ac.
60 |
61 |
62 | );
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/stories/with-libraries/css-in-js/emotion.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation } from "../../../src";
4 | import { css } from "@emotion/css";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [
12 | { transform: "rotate(0deg)", borderRadius: "1rem" },
13 | { transform: "rotate(360deg)", borderRadius: "50%" },
14 | { transform: "rotate(720deg)", borderRadius: "1rem" },
15 | ],
16 | {
17 | duration: 1000,
18 | iterations: Infinity,
19 | easing: "ease-in-out",
20 | }
21 | );
22 |
23 | useEffect(() => {
24 | animate.play();
25 | }, []);
26 |
27 | return (
28 | {
37 | animate.pause();
38 | }}
39 | onMouseLeave={() => {
40 | animate.play();
41 | }}
42 | />
43 | );
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/stories/with-libraries/css-in-js/linaria.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation } from "../../../src";
4 | import { styled } from '@linaria/react';
5 |
6 | export default { component: useAnimation };
7 |
8 | const Comp = styled.div`
9 | border: solid 0.1rem #135569;
10 | height: 6rem;
11 | width: 6rem;
12 | margin: 2rem 0 2rem 2rem;
13 | `;
14 |
15 | export const Default: StoryObj = {
16 | render: () => {
17 | const animate = useAnimation(
18 | [
19 | { transform: "rotate(0deg)", borderRadius: "1rem" },
20 | { transform: "rotate(360deg)", borderRadius: "50%" },
21 | { transform: "rotate(720deg)", borderRadius: "1rem" },
22 | ],
23 | {
24 | duration: 1000,
25 | iterations: Infinity,
26 | easing: "ease-in-out",
27 | }
28 | );
29 |
30 | useEffect(() => {
31 | animate.play();
32 | }, []);
33 |
34 | return (
35 |
{
38 | animate.pause();
39 | }}
40 | onMouseLeave={() => {
41 | animate.play();
42 | }}
43 | />
44 | );
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/stories/with-libraries/css-in-js/styled-components.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation } from "../../../src";
4 | import styled from "styled-components";
5 |
6 | export default { component: useAnimation };
7 |
8 | const Comp = styled.div`
9 | border: solid 0.1rem #135569;
10 | height: 6rem;
11 | width: 6rem;
12 | margin: 2rem 0 2rem 2rem;
13 | `;
14 |
15 | export const Default: StoryObj = {
16 | render: () => {
17 | const animate = useAnimation(
18 | [
19 | { transform: "rotate(0deg)", borderRadius: "1rem" },
20 | { transform: "rotate(360deg)", borderRadius: "50%" },
21 | { transform: "rotate(720deg)", borderRadius: "1rem" },
22 | ],
23 | {
24 | duration: 1000,
25 | iterations: Infinity,
26 | easing: "ease-in-out",
27 | }
28 | );
29 |
30 | useEffect(() => {
31 | animate.play();
32 | }, []);
33 |
34 | return (
35 | {
38 | animate.pause();
39 | }}
40 | onMouseLeave={() => {
41 | animate.play();
42 | }}
43 | />
44 | );
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/stories/with-libraries/css-in-js/vanilla-extract.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from "@vanilla-extract/css";
2 |
3 | export const myStyle = style({
4 | border: "solid 0.1rem #135569",
5 | height: "6rem",
6 | width: "6rem",
7 | margin: " 2rem 0 2rem 2rem",
8 | });
9 |
--------------------------------------------------------------------------------
/stories/with-libraries/css-in-js/vanilla-extract.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React, { useEffect } from "react";
3 | import { useAnimation } from "../../../src";
4 | import { myStyle } from "./vanilla-extract.css";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [
12 | { transform: "rotate(0deg)", borderRadius: "1rem" },
13 | { transform: "rotate(360deg)", borderRadius: "50%" },
14 | { transform: "rotate(720deg)", borderRadius: "1rem" },
15 | ],
16 | {
17 | duration: 1000,
18 | iterations: Infinity,
19 | easing: "ease-in-out",
20 | }
21 | );
22 |
23 | useEffect(() => {
24 | animate.play();
25 | }, []);
26 |
27 | return (
28 | {
32 | animate.pause();
33 | }}
34 | onMouseLeave={() => {
35 | animate.play();
36 | }}
37 | />
38 | );
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/stories/with-libraries/ui-components/Ant Design.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React from "react";
3 | import { Button } from "antd";
4 | import { useAnimation } from "../../../src";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
12 | {
13 | duration: 1000,
14 | easing: "ease-in-out",
15 | }
16 | );
17 |
18 | return (
19 |
{
22 | animate.play();
23 | }}
24 | >
25 | Click Me!
26 |
27 | );
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/stories/with-libraries/ui-components/Chakra UI.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React from "react";
3 | import { ChakraProvider, Button } from "@chakra-ui/react";
4 | import { useAnimation } from "../../../src";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
12 | {
13 | duration: 1000,
14 | easing: "ease-in-out",
15 | }
16 | );
17 |
18 | return (
19 |
20 | {
24 | animate.play();
25 | }}
26 | >
27 | Click Me!
28 |
29 |
30 | );
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/stories/with-libraries/ui-components/Fluent UI.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React from "react";
3 | import { PrimaryButton } from "@fluentui/react";
4 | import { useAnimation } from "../../../src";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
12 | {
13 | duration: 1000,
14 | easing: "ease-in-out",
15 | }
16 | );
17 |
18 | return (
19 |
{
22 | animate.play();
23 | }}
24 | >
25 | Click Me!
26 |
27 | );
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/stories/with-libraries/ui-components/Material UI.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React from "react";
3 | import Button from "@mui/material/Button";
4 | import { useAnimation } from "../../../src";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
12 | {
13 | duration: 1000,
14 | easing: "ease-in-out",
15 | }
16 | );
17 |
18 | return (
19 |
{
23 | animate.play();
24 | }}
25 | >
26 | Click Me!
27 |
28 | );
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/stories/with-libraries/ui-components/mantine.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj } from "@storybook/react";
2 | import React from "react";
3 | import { MantineProvider, Button } from "@mantine/core";
4 | import { useAnimation } from "../../../src";
5 |
6 | export default { component: useAnimation };
7 |
8 | export const Default: StoryObj = {
9 | render: () => {
10 | const animate = useAnimation(
11 | [{ transform: "rotate(0deg)" }, { transform: "rotate(720deg)" }],
12 | {
13 | duration: 1000,
14 | easing: "ease-in-out",
15 | }
16 | );
17 |
18 | return (
19 |
20 | {
23 | animate.play();
24 | }}
25 | >
26 | Click Me!
27 |
28 |
29 | );
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2018",
4 | "module": "esnext",
5 | "moduleResolution": "bundler",
6 | "jsx": "react-jsx",
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "strict": true,
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noUncheckedIndexedAccess": true,
19 | "noPropertyAccessFromIndexSignature": true,
20 | "noImplicitReturns": true,
21 | "noImplicitOverride": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "allowUnreachableCode": false,
24 | "allowUnusedLabels": false,
25 | "exactOptionalPropertyTypes": true,
26 | "stripInternal": true
27 | },
28 | "include": ["src"],
29 | "exclude": ["node_modules"]
30 | }
31 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["./src/index.ts"],
3 | "out": "docs",
4 | "name": "API",
5 | "plugin": ["typedoc-plugin-markdown"],
6 | "readme": "none",
7 | "sort": ["kind"],
8 | "groupOrder": ["Function", "Interface", "TypeAlias"],
9 | "githubPages": false,
10 | "entryDocument": "API.md",
11 | "hideBreadcrumbs": true
12 | }
13 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | root: "src",
6 | environment: "jsdom",
7 | clearMocks: true,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------