├── .gitignore
├── example
├── src
│ ├── templates
│ │ └── index.html
│ └── app
│ │ ├── app.css
│ │ └── app.tsx
├── tsconfig.json
├── package.json
└── webpack.config.js
├── .editorconfig
├── src
├── index.ts
├── animation-context.ts
├── AnimationGroup.tsx
├── easing.ts
├── use-anim.ts
└── use-anim.test.tsx
├── jest.config.js
├── LICENSE
├── package.json
├── README.md
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /lib/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/example/src/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | end_of_line = lf
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import AnimationGroup from './AnimationGroup'
2 | export { AnimationGroup }
3 |
4 | import { useAnim } from './use-anim'
5 | export { useAnim }
6 |
7 | import easing from './easing'
8 | export { easing }
9 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.tsx?$": "ts-jest",
4 | },
5 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(tsx?)$",
6 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
7 | testPathIgnorePatterns: [`node_modules`]
8 | }
9 |
--------------------------------------------------------------------------------
/src/animation-context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export interface AnimationContextProps {
4 | getStagger: () => number
5 | increaseStagger: () => void
6 | started?: boolean
7 | }
8 |
9 | export const AnimationContext = React.createContext(undefined)
10 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "experimentalDecorators": true,
8 | "target": "es5",
9 | "jsx": "react",
10 | "lib": [
11 | "dom",
12 | "es6"
13 | ]
14 | },
15 | "include": [
16 | "src"
17 | ],
18 | "compileOnSave": false
19 | }
20 |
--------------------------------------------------------------------------------
/src/AnimationGroup.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 | import { AnimationContext } from './animation-context'
3 |
4 | interface Props {
5 | stagger?: number
6 | started?: boolean
7 | children: ReactNode
8 | }
9 |
10 | const AnimationGroup: React.FC = (props: Props) => {
11 | let stagger = 0
12 |
13 | return (
14 | stagger,
17 | increaseStagger: () => stagger = stagger + (props.stagger || 0),
18 | }}>
19 | {props.children}
20 |
21 | )
22 | }
23 |
24 | export default AnimationGroup
25 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-anim-example",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "private": true,
6 | "scripts": {
7 | "build": "webpack -p",
8 | "start": "webpack-dev-server -d --content-base ./public"
9 | },
10 | "dependencies": {
11 | "react": "^16.8.0",
12 | "react-dom": "^16.8.0"
13 | },
14 | "devDependencies": {
15 | "clean-webpack-plugin": "^2.0.2",
16 | "css-loader": "^2.1.1",
17 | "html-webpack-plugin": "^3.2.0",
18 | "style-loader": "^0.23.1",
19 | "ts-loader": "^6.0.0",
20 | "webpack": "^4.30.0",
21 | "webpack-cli": "^3.3.2",
22 | "webpack-dev-server": "^3.3.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const CleanWebpackPlugin = require('clean-webpack-plugin');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | entry: './src/app/app.tsx',
6 | plugins: [
7 | new CleanWebpackPlugin(),
8 | new HtmlWebpackPlugin({
9 | template: 'src/templates/index.html'
10 | }),
11 | ],
12 | output: {
13 | path: __dirname + '/public',
14 | filename: 'build/[name].[contenthash].js'
15 | },
16 | resolve: {
17 | extensions: ['.ts', '.tsx', '.js']
18 | },
19 | module: {
20 | rules: [
21 | { test: /\.tsx?$/, loader: 'ts-loader' },
22 | { test: /\.css$/, use: ['style-loader', 'css-loader'] }
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/src/app/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #282c34;
3 | margin: 0;
4 | padding: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
6 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
7 | sans-serif;
8 | -webkit-font-smoothing: antialiased;
9 | -moz-osx-font-smoothing: grayscale;
10 | }
11 |
12 | .container {
13 | text-align: left;
14 | color: white;
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | align-items: center;
19 | min-height: 100vh;
20 | }
21 |
22 | .ball {
23 | width: 100px;
24 | height: 100px;
25 | background: white;
26 | }
27 |
28 | .text {
29 | width: 100%;
30 | }
31 |
32 | .staggered-box-container {
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | margin-top: 60px;
37 | }
38 |
39 | .staggered-box {
40 | width: 20px;
41 | height: 20px;
42 | background: white;
43 | margin: 10px;
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mathias
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-anim",
3 | "version": "2.0.0",
4 | "description": "A super small hook-based animation library for React",
5 | "main": "lib/index",
6 | "scripts": {
7 | "build": "tsc",
8 | "test": "jest",
9 | "copy-to-example": "rm example/node_modules/use-anim -rf && cp lib example/node_modules/use-anim -rf"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/mathiassoeholm/use-anim.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "animation",
18 | "hook"
19 | ],
20 | "author": "Mathias Soeholm",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/mathiassoeholm/use-anim/issues"
24 | },
25 | "homepage": "https://github.com/mathiassoeholm/use-anim#readme",
26 | "devDependencies": {
27 | "@mathiassoeholm/js-utils": "^1.0.9",
28 | "@types/jest": "^24.0.12",
29 | "@types/node": "^12.0.0",
30 | "@types/react": "^16.8.0",
31 | "jest": "^24.8.0",
32 | "jest-each": "^24.8.0",
33 | "react": "^16.8.0",
34 | "react-dom": "^16.8.0",
35 | "react-hooks-testing-library": "^0.5.0",
36 | "react-test-renderer": "^16.8.6",
37 | "ts-jest": "^24.0.2",
38 | "typescript": "^3.4.5"
39 | },
40 | "peerDependencies": {
41 | "react": "^16.8.0"
42 | },
43 | "files": [
44 | "README.md",
45 | "LICENSE",
46 | "/lib",
47 | "/src"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/src/easing.ts:
--------------------------------------------------------------------------------
1 | // Source: https://gist.github.com/gre/1650294
2 | export default {
3 | // no easing, no acceleration
4 | linear: function (t: number) { return t },
5 | // accelerating from zero velocity
6 | easeInQuad: function (t: number) { return t*t },
7 | // decelerating to zero velocity
8 | easeOutQuad: function (t: number) { return t*(2-t) },
9 | // acceleration until halfway, then deceleration
10 | easeInOutQuad: function (t: number) { return t<.5 ? 2*t*t : -1+(4-2*t)*t },
11 | // accelerating from zero velocity
12 | easeInCubic: function (t: number) { return t*t*t },
13 | // decelerating to zero velocity
14 | easeOutCubic: function (t: number) { return (--t)*t*t+1 },
15 | // acceleration until halfway, then deceleration
16 | easeInOutCubic: function (t: number) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 },
17 | // accelerating from zero velocity
18 | easeInQuart: function (t: number) { return t*t*t*t },
19 | // decelerating to zero velocity
20 | easeOutQuart: function (t: number) { return 1-(--t)*t*t*t },
21 | // acceleration until halfway, then deceleration
22 | easeInOutQuart: function (t: number) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t },
23 | // accelerating from zero velocity
24 | easeInQuint: function (t: number) { return t*t*t*t*t },
25 | // decelerating to zero velocity
26 | easeOutQuint: function (t: number) { return 1+(--t)*t*t*t*t },
27 | // acceleration until halfway, then deceleration
28 | easeInOutQuint: function (t: number) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t },
29 | // slighlty overshoots the target for an elastic effect
30 | easeOutBack: function (t: number) { return (t=t-1)*t*((1.70158+1)*t + 1.70158) + 1 },
31 | }
32 |
--------------------------------------------------------------------------------
/example/src/app/app.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './app.css'
4 | import { useAnim, easing, AnimationGroup } from 'use-anim'
5 | import { lerp } from '@mathiassoeholm/js-utils/math'
6 |
7 | const App: React.FC = () => {
8 | const [translation, setTranslation] = useState(0);
9 | const [rotation, setRotation] = useState(0);
10 | const [text, setText] = useState('')
11 | const [opacity, setOpacity] = useState(0)
12 |
13 | useAnim({
14 | duration: 2000,
15 | playMode: 'pingPong',
16 | easing: easing.easeInOutQuad,
17 | }, t => {
18 | setTranslation(lerp(-300, 300, t))
19 | setRotation(t*360)
20 | });
21 |
22 | useAnim({
23 | duration: 8000,
24 | }, t => {
25 | let text = 'This text is being typed by use-anim :-)'
26 | setText(text.slice(0, Math.round(text.length*t)))
27 | })
28 |
29 | useAnim({
30 | duration: 1000,
31 | }, t => {
32 | setOpacity(t)
33 | })
34 |
35 | return (
36 |
37 |
{text}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | const StaggeredBox: React.FC = () => {
54 | const [translation, setTranslation] = useState(-200)
55 |
56 | useAnim({
57 | duration: 800,
58 | easing: easing.easeOutBack,
59 | }, t => {
60 | setTranslation(lerp(-200, 200, t))
61 | })
62 |
63 | return
64 | }
65 |
66 | ReactDOM.render(
67 | ,
68 | document.getElementById("root")
69 | );
70 |
--------------------------------------------------------------------------------
/src/use-anim.ts:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from "react"
2 | import {AnimationContext, AnimationContextProps} from "./animation-context"
3 |
4 | interface AnimationConfig {
5 | duration: number,
6 | easing?: Easing,
7 | started?: boolean,
8 | playMode?: PlayMode,
9 | }
10 |
11 | type UpdateFunc = (t: number) => void
12 | type Easing = (t: number) => number
13 | type PlayMode = 'forward' | 'reverse' | 'loop' | 'pingPong'
14 |
15 | export function useAnim(config: AnimationConfig, updateFunc: UpdateFunc) {
16 | // Auto start animation if no other options override this
17 | let shouldStart = true
18 |
19 | const animationContext = useContext(AnimationContext)
20 |
21 | if (animationContext && animationContext.started != undefined) {
22 | shouldStart = animationContext.started
23 | }
24 |
25 | if (config.started != undefined) {
26 | shouldStart = config.started
27 | }
28 |
29 | const [startedAnimation, setStartedAnimation] = useState(shouldStart)
30 |
31 | if (!startedAnimation && shouldStart) {
32 | setStartedAnimation(true)
33 | }
34 |
35 | useEffect(
36 | __createUseAnimEffect(startedAnimation, animationContext, config, updateFunc),
37 | [startedAnimation],
38 | )
39 |
40 | return { startedAnimation }
41 | }
42 |
43 | // This function is only exported, such that it can be used in tests.
44 | export const __createUseAnimEffect = (
45 | startedAnimation: boolean,
46 | animationContext: AnimationContextProps|undefined,
47 | config: AnimationConfig,
48 | updateFunc: UpdateFunc,
49 | ) => () => {
50 | if (!startedAnimation) {
51 | return
52 | }
53 |
54 | let currentFrame: number|undefined = undefined
55 | let delay = 0
56 |
57 | if (animationContext) {
58 | delay = animationContext.getStagger()
59 | animationContext.increaseStagger()
60 | }
61 |
62 | setTimeout(() => {
63 | let startTime = Date.now()
64 | let reverse = config.playMode === 'reverse'
65 |
66 | const update = () => {
67 | const elapsed = Date.now() - startTime
68 | let t = Math.min(1, elapsed/config.duration)
69 |
70 | if (reverse) {
71 | t = 1-t
72 | }
73 |
74 | if (config.easing) {
75 | if (reverse) {
76 | t = 1 - config.easing(1-t)
77 | } else {
78 | t = config.easing(t)
79 | }
80 | }
81 |
82 | updateFunc(t)
83 |
84 | if (elapsed < config.duration) {
85 | currentFrame = requestAnimationFrame(update)
86 | } else if (config.playMode === 'loop' || config.playMode === 'pingPong') {
87 | startTime = Date.now()
88 |
89 | if (config.playMode === 'pingPong') {
90 | reverse = !reverse
91 | }
92 |
93 | currentFrame = requestAnimationFrame(update)
94 | }
95 | }
96 |
97 | currentFrame = requestAnimationFrame(update)
98 | }, delay)
99 |
100 | return () => {
101 | if (currentFrame != undefined) {
102 | cancelAnimationFrame(currentFrame)
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## use-anim
2 |
3 | [](https://bundlephobia.com/result?p=use-anim)
4 | [](https://www.npmjs.com/package/use-anim)
5 |
6 | Super small easy-to-use hook-based animation library for React.
7 |
8 | * Zero dependencies
9 | * Written in TypeScript
10 | * Very small code-base
11 |
12 | Check it out on [CodeSandbox](https://codesandbox.io/s/4kox1m1m7?fontsize=14)!
13 |
14 | ### Installation
15 |
16 | npm install use-anim
17 |
18 | or
19 |
20 | yarn add use-anim
21 |
22 | ### Usage
23 |
24 | ```javascript
25 | import React, {useState} from 'react'
26 | import { useAnim, easing } from 'use-anim'
27 |
28 | // A component that makes its children
29 | // appear with a fancy fade animation
30 | const FadeReveal = ({ children }) => {
31 | const [opacity, setOpacity] = useState(0)
32 | const [translation, setTranslation] = useState(-30)
33 |
34 | useAnim({
35 | duration: 500,
36 | easing: easing.easeOutQuad,
37 | }, t => {
38 | setOpacity(t)
39 | setTranslation(-30*(1-t))
40 | })
41 |
42 | return (
43 |
44 | {children}
45 |
46 | )
47 | }
48 | ```
49 |
50 | ### AnimationConfig
51 |
52 | To start an animation you give useAnim an object compatible with the following interface as the first parameter:
53 |
54 | ```typescript
55 | interface AnimationConfig {
56 | duration: number,
57 | easing?: Easing,
58 | started?: boolean,
59 | playMode?: PlayMode,
60 | }
61 | ```
62 |
63 | **duration:** How long does the animation run in milliseconds.
64 |
65 | **easing:** A function that accepts a value `t` in range 0-1 and returns a modified number in the same range. This is used to get a more smooth animation.
66 |
67 | **started:** If the animation should not start automatically, you can set this value to `false`. The animation will start when this is changed to `true`.
68 |
69 | **playMode:** Controls playback of the animation. Valid values are: `forward` (default), `reverse`, `loop` and `pingPong`.
70 |
71 | The second parameter, known as `updateFunc`, is a function which takes a parameter `t`. This is called every frame during the animation, with `t` running from 0 to 1.
72 |
73 | ### Drawbacks 😕
74 | [As pointed out by a good Reddit user](https://www.reddit.com/r/reactjs/comments/bnqcnm/i_created_a_super_small_animation_library_for/enbdxw3?utm_source=share&utm_medium=web2x), using `setState` every frame can be expensive. It will trigger a rerender of the component and all its children. Therefore I recommend using this library for simple components that reside near the leafs.
75 |
76 | ### Contributing 👍
77 |
78 | Pull requests are very welcome
79 |
80 | To test locally, you can use:
81 |
82 | npm run build
83 | npm run copy-to-example
84 |
85 | Note: The copy-to-example script uses the `cp` command which is not available through CMD on Windows. You can use Git Bash instead.
86 |
87 | This will compile TypeScript to JavaScript in a folder called lib and copy this folder to example/node_modules/use-anim.
88 | You can now test your new addition in the example project.
89 |
90 | cd example
91 | npm start
92 |
93 | Note: Remember to run `npm install` in both the root directory and the example directory.
94 |
--------------------------------------------------------------------------------
/src/use-anim.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import each from 'jest-each';
3 | import { renderHook } from 'react-hooks-testing-library'
4 | import { delay } from '@mathiassoeholm/js-utils/async'
5 | import {__createUseAnimEffect, useAnim} from './use-anim'
6 | import AnimationGroup from './AnimationGroup'
7 |
8 | describe('use-anim', () => {
9 |
10 | const doNothing = () => {}
11 |
12 | it('starts animation by default', async () => {
13 | const { result } = renderHook(() =>
14 | useAnim({
15 | duration: 1000,
16 | }, doNothing)
17 | )
18 |
19 | expect(result.current.startedAnimation).toBe(true)
20 | })
21 |
22 | it('does not start the animation if `started` is false', async () => {
23 | const { result } = renderHook(() =>
24 | useAnim({
25 | duration: 1000,
26 | started: false,
27 | }, doNothing)
28 | )
29 |
30 | expect(result.current.startedAnimation).toBe(false)
31 | })
32 |
33 | it('does not start the animation if `started` is false on AnimationGroup', async () => {
34 | const { result } = renderHook(() =>
35 | useAnim({
36 | duration: 1000,
37 | }, doNothing),
38 | {
39 | wrapper: ({children}) => (
40 |
41 | {children}
42 |
43 | )
44 | },
45 | )
46 |
47 | expect(result.current.startedAnimation).toBe(false)
48 | })
49 |
50 | it('starts the animation if `started` is true on AnimationGroup', async () => {
51 | const { result } = renderHook(() =>
52 | useAnim({
53 | duration: 1000,
54 | }, doNothing),
55 | {
56 | wrapper: ({children}) => (
57 |
58 | {children}
59 |
60 | )
61 | },
62 | )
63 |
64 | expect(result.current.startedAnimation).toBe(true)
65 | })
66 |
67 | it('respects the inner most `started` flag', async () => {
68 | const { result } = renderHook(() =>
69 | useAnim({
70 | duration: 1000,
71 | started: true,
72 | }, doNothing),
73 | {
74 | wrapper: ({children}) => (
75 |
76 | {children}
77 |
78 | )
79 | },
80 | )
81 |
82 | expect(result.current.startedAnimation).toBe(true)
83 | })
84 |
85 | it('uses inner most AnimationGroup', async () => {
86 | const { result } = renderHook(() =>
87 | useAnim({
88 | duration: 1000,
89 | }, doNothing),
90 | {
91 | wrapper: ({children}) => (
92 |
93 |
94 | {children}
95 |
96 |
97 | )
98 | },
99 | )
100 |
101 | expect(result.current.startedAnimation).toBe(false)
102 | })
103 |
104 | it('does not call update func if not started', async () => {
105 | const animEffect = __createUseAnimEffect(false, undefined, {
106 | duration: 50,
107 | }, () => fail())
108 |
109 | animEffect()
110 |
111 | await delay(50)
112 | })
113 |
114 | it('can be started and stopped', async () => {
115 | let running = false
116 |
117 | const animEffect = __createUseAnimEffect(true, undefined, {
118 | duration: 1000000,
119 | }, () => running = true)
120 |
121 | const stopEffect = animEffect()
122 |
123 | await delay(50)
124 | expect(running).toBe(true)
125 |
126 | running = false
127 | stopEffect!()
128 |
129 | await delay(50)
130 | expect(running).toBe(false)
131 | })
132 |
133 | each([
134 | ['loop'],
135 | ['pingPong'],
136 | ]).test('keeps running with play mode %s', async (playMode) => {
137 | let running = false
138 |
139 | const animEffect = __createUseAnimEffect(true, undefined, {
140 | duration: 5,
141 | playMode: playMode,
142 | }, () => running = true)
143 |
144 | animEffect()
145 |
146 | // Should be done after this delay, unless it's looping
147 | await delay(50)
148 |
149 | running = false
150 |
151 | // If running, allow it some time to set running to true
152 | await delay(50)
153 |
154 | expect(running).toBe(true)
155 | })
156 |
157 |
158 | it('ping pongs', async () => {
159 |
160 | let expectedValue = 1
161 |
162 | const animEffect = __createUseAnimEffect(true, undefined, {
163 | duration: 0,
164 | playMode: 'pingPong',
165 | }, (t) => {
166 | expect(t).toBe(expectedValue)
167 | expectedValue = 1 - expectedValue
168 | })
169 |
170 | animEffect()
171 |
172 | await delay(150)
173 | })
174 |
175 | it('starts at 1 when play mode is reverse', async () => {
176 | const animEffect = __createUseAnimEffect(true, undefined, {
177 | duration: Infinity,
178 | playMode: 'reverse',
179 | }, (t) => {
180 | expect(t).toBe(1)
181 | })
182 |
183 | animEffect()
184 |
185 | await delay(50)
186 | })
187 | })
188 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | // "lib": [], /* Specify library files to be included in the compilation. */
7 | // "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | // "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | "outDir": "./lib", /* Redirect output structure to the directory. */
15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "incremental": true, /* Enable incremental compilation */
18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
19 | // "removeComments": true, /* Do not emit comments to output. */
20 | // "noEmit": true, /* Do not emit outputs. */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 |
35 | /* Additional Checks */
36 | // "noUnusedLocals": true, /* Report errors on unused locals. */
37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40 |
41 | /* Module Resolution Options */
42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
46 | // "typeRoots": [], /* List of folders to include type definitions from. */
47 | // "types": [], /* Type declaration files to be included in compilation. */
48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
51 |
52 | /* Source Map Options */
53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
57 |
58 | /* Experimental Options */
59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
61 | "lib": [
62 | "dom",
63 | "es2015"
64 | ]
65 | },
66 | "include": ["src"]
67 | }
68 |
--------------------------------------------------------------------------------