├── .editorconfig
├── .eslintrc.js
├── .fatherrc.js
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .umirc.ts
├── HISTORY.md
├── LICENSE
├── README.md
├── animate-param.md
├── assets
└── index.less
├── docs
├── changeLog.md
├── demo
│ ├── animParam.md
│ ├── attr.md
│ ├── blurFilter.md
│ ├── component.md
│ ├── componentFragment.md
│ ├── control.md
│ ├── easingPath.md
│ ├── followMouse.md
│ ├── group.md
│ ├── group:useInTable.md
│ ├── plugin-gradient.md
│ ├── plugin-number-dance.md
│ ├── plugin-path-motion.md
│ ├── plugin-svg-draw.md
│ ├── plugin-svg-morph.md
│ ├── resetStyle.md
│ ├── simple.md
│ └── type.md
├── examples
│ ├── animParam.tsx
│ ├── attr.tsx
│ ├── blurFilter.tsx
│ ├── component.tsx
│ ├── componentFragment.tsx
│ ├── control.tsx
│ ├── easingPath.tsx
│ ├── followMouse.tsx
│ ├── group.tsx
│ ├── numberDance.tsx
│ ├── pathMotion.tsx
│ ├── resetStyle.tsx
│ ├── simple.tsx
│ ├── svg.tsx
│ ├── svgDraw.tsx
│ ├── type.tsx
│ ├── useInTable.less
│ └── useInTable.tsx
└── index.md
├── index.d.ts
├── now.json
├── package.json
├── src
├── TweenOne.tsx
├── TweenOneGroup.tsx
├── index.tsx
├── plugin
│ ├── ChildrenPlugin.ts
│ ├── PathMotionPlugin.ts
│ ├── SvgDrawPlugin.ts
│ └── SvgMorphPlugin.ts
├── type.ts
└── utils
│ ├── common.ts
│ ├── group.ts
│ └── index.ts
├── tests
├── tweenOne.test.jsx
└── tweenOneGroup.test.jsx
├── tsconfig.json
└── typings
└── global
├── import.d.ts
└── index.d.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const base = require('@umijs/fabric/dist/eslint');
2 |
3 | module.exports = {
4 | ...base,
5 | rules: {
6 | ...base.rules,
7 | 'import/no-extraneous-dependencies': 0,
8 | 'import/no-named-as-default': 0,
9 | 'no-template-curly-in-string': 0,
10 | 'prefer-promise-reject-errors': 0,
11 | 'react/no-array-index-key': 0,
12 | 'react/require-default-props': 0,
13 | 'react/sort-comp': 0,
14 | 'react/no-find-dom-node': 1,
15 | '@typescript-eslint/no-explicit-any': 0,
16 | 'jsx-a11y/label-has-associated-control': 0,
17 | 'jsx-a11y/label-has-for': 0,
18 | 'no-param-reassign': 0,
19 | 'prefer-destructuring': 0,
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/.fatherrc.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | cjs: 'babel',
4 | esm: { type: 'babel', importLibToEs: true },
5 | runtimeHelpers: true,
6 | preCommit: {
7 | eslint: true,
8 | prettier: true,
9 | },
10 | };
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master, 3.0]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | setup:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: checkout
14 | uses: actions/checkout@master
15 |
16 | - uses: actions/setup-node@v1
17 | with:
18 | node-version: '12'
19 |
20 | - name: cache package-lock.json
21 | uses: actions/cache@v2
22 | with:
23 | path: package-temp-dir
24 | key: lock-${{ github.sha }}
25 |
26 | - name: create package-lock.json
27 | run: npm i --package-lock-only
28 |
29 | - name: hack for singe file
30 | run: |
31 | if [ ! -d "package-temp-dir" ]; then
32 | mkdir package-temp-dir
33 | fi
34 | cp package-lock.json package-temp-dir
35 | - name: cache node_modules
36 | id: node_modules_cache_id
37 | uses: actions/cache@v2
38 | with:
39 | path: node_modules
40 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
41 |
42 | - name: install
43 | if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
44 | run: npm ci
45 |
46 | lint:
47 | runs-on: ubuntu-latest
48 | steps:
49 | - name: checkout
50 | uses: actions/checkout@master
51 |
52 | - name: restore cache from package-lock.json
53 | uses: actions/cache@v2
54 | with:
55 | path: package-temp-dir
56 | key: lock-${{ github.sha }}
57 |
58 | - name: restore cache from node_modules
59 | uses: actions/cache@v2
60 | with:
61 | path: node_modules
62 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
63 |
64 | - name: lint
65 | run: npm run lint
66 |
67 | needs: setup
68 |
69 | compile:
70 | runs-on: ubuntu-latest
71 | steps:
72 | - name: checkout
73 | uses: actions/checkout@master
74 |
75 | - name: restore cache from package-lock.json
76 | uses: actions/cache@v2
77 | with:
78 | path: package-temp-dir
79 | key: lock-${{ github.sha }}
80 |
81 | - name: restore cache from node_modules
82 | uses: actions/cache@v2
83 | with:
84 | path: node_modules
85 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
86 |
87 | - name: compile
88 | run: npm run compile
89 |
90 | needs: setup
91 |
92 | coverage:
93 | runs-on: ubuntu-latest
94 | steps:
95 | - name: checkout
96 | uses: actions/checkout@master
97 |
98 | - name: restore cache from package-lock.json
99 | uses: actions/cache@v2
100 | with:
101 | path: package-temp-dir
102 | key: lock-${{ github.sha }}
103 |
104 | - name: restore cache from node_modules
105 | uses: actions/cache@v2
106 | with:
107 | path: node_modules
108 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
109 |
110 | - name: coverage
111 | run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash)
112 |
113 | needs: setup
114 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .storybook
2 | *.iml
3 | *.log
4 | .idea/
5 | .ipr
6 | .iws
7 | *~
8 | ~*
9 | *.diff
10 | *.patch
11 | *.bak
12 | .DS_Store
13 | Thumbs.db
14 | .project
15 | .*proj
16 | .svn/
17 | *.swp
18 | *.swo
19 | *.pyc
20 | *.pyo
21 | .build
22 | node_modules
23 | .cache
24 | dist
25 | assets/**/*.css
26 | build
27 | lib
28 | es
29 | coverage
30 | yarn.lock
31 | package-lock.json
32 | .vscode
33 | .doc
34 | # production
35 | /dist
36 | /docs-dist
37 |
38 | # misc
39 | .DS_Store
40 | # umi
41 | .umi
42 | .umi-production
43 | .umi-test
44 | .env.local
45 | src/.umi
46 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.cfg
3 | nohup.out
4 | *.iml
5 | .idea/
6 | .ipr
7 | .iws
8 | *~
9 | ~*
10 | *.diff
11 | *.log
12 | *.patch
13 | *.bak
14 | .DS_Store
15 | Thumbs.db
16 | .project
17 | .*proj
18 | .svn/
19 | *.swp
20 | out/
21 | .build
22 | node_modules
23 | .cache
24 | examples
25 | tests
26 | src
27 | /index.js
28 | .*
29 | assets/**/*.less
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | **/*.ejs
3 | **/*.html
4 | package.json
5 | .umi
6 | .umi-production
7 | .umi-test
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 100,
5 | "overrides": [
6 | {
7 | "files": ".prettierrc",
8 | "options": { "parser": "json" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.umirc.ts:
--------------------------------------------------------------------------------
1 | // more config: https://d.umijs.org/config
2 | import { defineConfig } from 'dumi';
3 | const path = require('path');
4 |
5 | export default defineConfig({
6 | title: 'rc-tween-one',
7 | favicon: 'https://zos.alipayobjects.com/rmsportal/TOXWfHIUGHvZIyb.svg',
8 | logo: 'https://zos.alipayobjects.com/rmsportal/TOXWfHIUGHvZIyb.svg',
9 | outputPath: '.doc',
10 | alias: {
11 | 'rc-tween-one/es': path.join(__dirname, 'src'),
12 | 'rc-tween-one/lib': path.join(__dirname, 'src'),
13 | },
14 | });
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # History
2 | ----
3 |
4 | ## 2.2.0
5 | 1. resetStyleBool 改名成 resetStyle;
6 | 2. TweenOne 删除 updateReStart, 现在默认是。
7 | 3. Group 重构动画逻辑,以队列形式切换,如果在动画时做切换,需将动画完成后再执切换动画。
8 | 4. Group 增加 exclusive, 在队列动画时强行执行切换动画。
9 |
10 | ## 2.0.0
11 |
12 | add repeat and yoyo to tag;
13 |
14 | ## 1.8.0
15 |
16 | add children plugin.
17 |
18 | ## 0.4
19 |
20 | 1. filter 拆分为 'grayScale', 'sepia', 'hueRotate', 'invert', 'brightness', 'contrast', 'blur', 与transform一致;
21 |
22 | 2. 忧化时间轴
23 |
24 | 3. 忧化 yoyo 在数组最后个卡顿的问题。
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT LICENSE
2 |
3 | Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # rc-tween-one
3 | ---
4 |
5 | React TweenOne Component
6 |
7 |
8 | [![NPM version][npm-image]][npm-url]
9 | [![build status][github-actions-image]][github-actions-url]
10 | [![Codecov][codecov-image]][codecov-url]
11 | [![node version][node-image]][node-url]
12 | [![npm download][download-image]][download-url]
13 |
14 | [npm-image]: http://img.shields.io/npm/v/rc-tween-one.svg?style=flat-square
15 | [npm-url]: http://npmjs.org/package/rc-tween-one
16 | [github-actions-image]: https://github.com/react-component/tween-one/workflows/CI/badge.svg
17 | [github-actions-url]: https://github.com/react-component/tween-one/actions
18 | [codecov-image]: https://img.shields.io/codecov/c/github/react-component/tween-one/master.svg?style=flat-square
19 | [codecov-url]: https://codecov.io/gh/react-component/tween-one/branch/master
20 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square
21 | [node-url]: http://nodejs.org/download/
22 | [download-image]: https://img.shields.io/npm/dm/rc-tween-one.svg?style=flat-square
23 | [download-url]: https://npmjs.org/package/rc-tween-one
24 |
25 |
26 | ## Browser Support
27 |
28 | | |  |  |  | |
29 | | --- | --- | --- | --- | --- |
30 | | IE 10+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ |
31 |
32 | ## Development
33 |
34 | ```
35 | npm install
36 | npm start
37 | ```
38 |
39 | ## Example
40 |
41 | http://localhost:8100/examples/
42 |
43 | 2.x: http://react-component.github.io/tween-one/
44 |
45 | 3.x: https://tween-one.vercel.app/
46 |
47 | ## install
48 |
49 |
50 | [](https://npmjs.org/package/rc-tween-one)
51 |
52 |
53 | ## Usage
54 |
55 | ```js | pure
56 | var TweenOne = require('rc-tween-one');
57 | var React = require('react');
58 | React.render(
59 | demo
60 | , container);
61 | ```
62 |
63 | ### Plugin
64 |
65 | ```js | pure
66 | var TweenOne = require('rc-tween-one');
67 | var React = require('react');
68 | var SvgDrawPlugin = require('rc-tween-one/lib/plugin/SvgDrawPlugin');
69 | TweenOne.plugins.push(SvgDrawPlugin);
70 | React.render(, container);
78 | ```
79 |
80 | ### TweenOneGroup
81 | ```js | pure
82 | var TweenOne = require('rc-tween-one');
83 | var React = require('react');
84 | var TweenOneGroup = TweenOne.TweenOneGroup;
85 | React.render(
86 | demo
87 | demo
88 | , container);
89 | ```
90 |
91 | ## API
92 |
93 | 中文文档
94 |
95 | ### props
96 |
97 | | name | type | default | description |
98 | |------------|----------------|---------|----------------|
99 | | animation | object / array | null | animate configure parameters |
100 | | paused | boolean | false | animate timeline pause |
101 | | reverse | boolean | false | animate timeline revers |
102 | | delay | number | 0 | animate timeline delay |
103 | | repeat | number | 0 | `animation` all data repeat, To repeat indefinitely, use -1 |
104 | | repeatDelay | number | 0 | animate timeline repeat delay |
105 | | yoyo | boolean | false | `animation` all data alternating backward and forward on each repeat. |
106 | | onChange | func | null | when the animation change called, callback({ moment, targets, index, mode, ratio, vars, index, repeat }) |
107 | | onChangeTimeline | func | null | when the animation change called, callback({ mode, targets, vars, moment, totalTime, repeat }) |
108 | | moment | number | null | set the current frame |
109 | | regionStartTime | number | 0 | Set the start time of the animation region |
110 | | regionEndTime | number | null | Set the end time of the animation region |
111 | | attr | boolean | false | attribute animation is `true`, when morph SVG must be `true`. |
112 | | resetStyle | boolean | false | update animation data, reset init style |
113 | | component | string / React.Element | `div` | component tag |
114 | | componentProps | object | null | component is React.Element, component tag props, not add `style` |
115 |
116 |
117 | ### animation = { }
118 |
119 | > Basic animation param. please view [animation terms](https://motion.ant.design/language/animate-term)
120 |
121 | | name | type | default | description |
122 | |------------|----------------|---------|----------------|
123 | | [key: string] | `string` `number` `array` | null | All variables based on number, such as left, x, color, shadow |
124 | | type | string | `to` | play type: `to` `from` `set`|
125 | | duration | number | 450 | animate duration |
126 | | delay | number | 0 | animate delay |
127 | | repeat | number | 0 | animate repeat, To repeat indefinitely, use -1 |
128 | | repeatDelay| number | 0 | repeat start delay |
129 | | appearTo | number | null | Add to the specified time |
130 | | yoyo | boolean | false | `true`: alternating backward and forward on each repeat. |
131 | | ease | string | `easeInOutQuad` | animate ease [refer](http://easings.net/en) or svg path `M0,100 C30,60 0,20 50,50 C70,70 60,0 100,0` |
132 | | bezier | object | null | bezier curve animate |
133 | | onStart | func | null | A function that should be called when the tween begins, callback(e), e: { index, target } |
134 | | onUpdate | func | null | A function that should be called every time the animate updates, callback(e), e: { index, targets, ratio } |
135 | | onComplete | func | null | A function that should be called when the animate has completed, callback(e), e: { index, targets } |
136 | | onRepeat | func | null | A function that should be called each time the animate repeats, callback(e), e: { index, targets } |
137 |
138 | > Cannot be used at the same time `reverse` and `repeat: -1`.
139 |
140 | ### animation =[ ] is timeline
141 | ```js | pure
142 |
143 | ```
144 | ## Plugins
145 | ### SvgDrawPlugin
146 |
147 | ```js | pure
148 | import { Plugins } from 'rc-tween-one';
149 | import SvgDrawPlugin from 'rc-tween-one/es/plugin/SvgDrawPlugin';
150 | Plugins.push(SvgDrawPlugin);
151 |
152 |
153 | ```
154 |
155 | SVGDraw = string or number;
156 |
157 | { SVGDraw: 30 } or { SVGDraw: 'start end' } start and end values can be `%`;
158 |
159 | ### SvgMorphPlugin
160 |
161 | ```js | pure
162 | import { Plugins } from 'rc-tween-one';
163 | import SvgMorphPlugin from 'rc-tween-one/es/plugin/SvgMorphPlugin';
164 | Plugins.push(SvgMorphPlugin);
165 |
166 |
167 | ```
168 | #### SvgMorphPlugin API
169 |
170 | | name | type | default | description |
171 | |------------------|--------|---------|----------------|
172 | | path | string | null | svg path, ref: `M0,0L100,0`;|
173 | | attr | string | null | Svg tag attributes, example: `polygon` is ` points`, `path` is `d`. |
174 | | maxSegmentLength | number | 0.5 | The lower the value, the smoother the generated animation will be, but at the expense of performance;|
175 |
176 |
177 | ### PathPlugin
178 |
179 | ```js | pure
180 | import { Plugins } from 'rc-tween-one';
181 | import PathMotionPlugin from 'rc-tween-one/es/plugin/PathMotionPlugin';
182 | Plugins.push(PathMotionPlugin);
183 |
184 |
185 | ```
186 | #### PathMotion API
187 |
188 | | name | type | default | description |
189 | | ------ | ------------------- | --------------- | ----------------------------- |
190 | | path | string / {x,y}[] | null | svg path, ref: `M0,0L100,0`; |
191 | | pathVars | IPathVars | null | Only valid if path is array `[{x, y}, {x, y}]` |
192 | | center | `number \ string[]` | `['50%','50%']` | center point, ref: `[50px, 50px]`; |
193 | | x | boolean | true | x follow the path. |
194 | | y | boolean | true | y follow the path. |
195 | | rotate | boolean | true | rotate follow the path. |
196 |
197 | ##### IPathVars
198 | | name | type | default | description |
199 | | ------ | ------------------- | --------------- | ----------------------------- |
200 | | type | `thru \ soft \ cubic` | `thru` | path type. `thru` same as the path; `soft` with the curve of attraction facing them, but not through the point; `cubic` allows you to define standard Cubic Bezier, example: `[start, control, control, end]`. |
201 | | curviness | 0-2 | 1 | This determines how "curvy" the resulting path is. `0` is lines, `1` is curved path, `2` would make it much more curvy. It can be `1.5`. |
202 | | relative | boolean | false | Increase relative to current value. example: if the target's x starts at 100 and the path is `[{x:5}, {x:10}, {x:-2}]` , it would first move to `105`, then `115`, and finally end at `113`. |
203 |
204 | ### ChildrenPlugin
205 |
206 | #### Children = { value:, floatLength, formatMoney };
207 |
208 | | name | type | default | description |
209 | |---|---|---|---|
210 | | value | number | null | children number to value. |
211 | | floatLength | number | null | float precision length |
212 | | formatMoney | `true` \ { thousand, decimal } | null | format number to money. |
213 |
214 | #### formatMoney = { thousand, decimal }
215 |
216 | | name | type | default | description |
217 | |---|---|---|---|
218 | | thousand | string | `,` | no explanation. |
219 | | decimal | string | `.` | no explanation. |
220 |
221 | ## TweenOneGroup
222 |
223 | | name | type | default | description |
224 | |------------|----------------|---------|----------------|
225 | | appear | boolean | true | whether support appear anim |
226 | | enter | object / array / func | `{ x: 30, opacity: 0, type: 'from' }` | enter anim twee-one data. when array is tween-one timeline, func refer to queue-anim |
227 | | leave | object / array / func | `{ x: 30, opacity: 0 }` | leave anim twee-one data. when array is tween-one timeline, func refer to queue-anim |
228 | | onEnd | func | - | one animation end callback |
229 | | animatingClassName | array | `['tween-one-entering', 'tween-one-leaving']` | className to every element of animating |
230 | | resetStyle | boolean | true | TweenOne resetStyle, reset the initial style when changing animation. |
231 | | exclusive | boolean | false | Whether to allow a new animate to execute immediately when switching. `enter => leave`: execute immediately leave |
232 | | component | React.Element/String | div | component tag |
233 | | componentProps | object | - | component tag props |
234 |
--------------------------------------------------------------------------------
/animate-param.md:
--------------------------------------------------------------------------------
1 | ## 动画基本参数说明
2 |
3 | tween-one 里的可支持 style 动画参数说明。
4 |
5 | 动画默认参数: duration: 450毫秒, ease: 'easeInOutQuad'; 以下文本中出现的`到`为默认动画。
6 |
7 | ### 基本 style 可动画参数
8 |
9 | | 参数名称 | 说明 |
10 | |-------------------|---------------------|
11 | | width | `{ width: 100 }` 元素当前宽度到 100px |
12 | | maxWidth | `{ maxWidth: 100 }` 元素当前最大宽度到 100px |
13 | | minWidth | `{ minWidth: 100 }` 元素当前最小宽度到 100px |
14 | | height | `{ height: 100 }` 元素当前高度到 100px |
15 | | maxHeight | `{ maxHeight: 100 }` 元素当前最大高度到 100px |
16 | | minHeight | `{ mimHeight: 100 }` 元素当前最小高度到 100px |
17 | | lineHeight | `{ lineHeight: 100 }` 区块行高到 100px |
18 | | opacity | `{ opacity: 0 }` 元素当前透明度到 0 |
19 | | top | `{ top: 100 }` 元素当前顶部距离到 100px, 需配合 `position: relative | absolute` |
20 | | right | `{ right: 100 }` 元素当前右部距离到 100px, 需配合 `position: relative | absolute` |
21 | | bottom | `{ bottom: 100 }` 元素当前下部距离到 100px, 需配合 `position: relative | absolute` |
22 | | left | `{ left: 100 }` 元素当前左部距离到 100px, 需配合 `position: relative | absolute` |
23 | | marginTop | `{ marginTop: 100 }` 元素当前顶部外边距离到 100px |
24 | | marginRight | `{ marginRight: 100 }` 元素当前右部外边距离到 100px |
25 | | marginBottom | `{ marginBottom: 100 }` 元素当前下部外边距离到 100px |
26 | | marginLeft | `{ marginLeft: 100 }` 元素当前左部外边距离到 100px |
27 | | paddingTop | `{ paddingTop: 100 }` 元素当前顶部内边距离到 100px |
28 | | paddingRight | `{ paddingRight: 100 }` 元素当前右部内边距离到 100px |
29 | | paddingBottom | `{ paddingBottom: 100 }` 元素当前下部内边距离到 100px |
30 | | paddingLeft | `{ paddingLeft: 100 }` 元素当前左部内边距离到 100px |
31 | | color | `{ color: '#FFFFFF' }` 元素当前文字颜色到白色 |
32 | | backgroundColor | `{ backgroundColor: '#FFFFFF' }` 元素当前背景颜色到白色 |
33 | | borderWidth | `{ borderWidth: 2 }` 元素当前边框宽度到 2px,同样可用 `borderTopWidth` `borderRightWidth` `borderBottomWidth` `borderLeftWidth` |
34 | | borderRadius | `{ borderRadius: 5 }` 元素当前圆角到 5px, 同上, 同样可用 `上 左 下 右` |
35 | | borderColor | `{ borderColor: '#FFFFFF' }` 元素当前边框颜色到白色 |
36 | | boxShadow | `{ boxShadow: '0 0 10px #000' }` 元素当前阴影模糊到 10px |
37 | | textShadow | `{ textShadow: '0 0 10px #000' }` 元素当前文字内容阴影模糊到 10px |
38 |
39 |
40 | ### transform 参数
41 |
42 | | 参数名称 | 说明 |
43 | |-------------------|---------------------|
44 | | translateX / X | `{ translateX: 10 } or { x: 10 } => transform: translateX(10px)`, x 方向的位置移动 10px |
45 | | translateY / y | `{ translateY: 10 } or { y: 10 } => transform: translateY(10px)`, y 方向的位置移动 10px |
46 | | translateZ / z | `{ translateZ: 10 } or { z: 10 } => transform: translateZ(10px)`, z 方向的位置移动 100px |
47 | | rotate | `{ rotate: 10 } => transform: rotate(10deg)` 元素以 transformOrigin 的中心点旋转 10deg |
48 | | rotateX | `{ rotateX: 10 } => transform: rotateX(10deg)` 元素以 transformOrigin 的中心点向 X 旋转 10deg |
49 | | rotateY | `{ rotateY: 10 } => transform: rotateY(10deg)` 元素以 transformOrigin 的中心点向 Y 旋转 10deg |
50 | | scale | `{ scale: 0 } => transform: scale(0)` 元素以 transformOrigin 的中心点缩放到 0 |
51 | | scaleX | `{ scaleX: 0 } => transform: scaleX(0)` 元素以 transformOrigin 的中心点 X 缩放到 0 |
52 | | scaleY | `{ scaleY: 0 } => transform: scaleY(0)` 元素以 transformOrigin 的中心点 Y 缩放到 0 |
53 | | transformOrigin | `{ transformOrigin: '50px 50px'}` 元素当前中心点到 x: 50px y: 50px |
54 |
55 | ### filter 参数
56 |
57 | | 参数名称 | 说明 |
58 | |-------------------|---------------------|
59 | | grayScale | `{ grayScale: 1 }` 元素 filter 灰度到 100% |
60 | | sepia | `{ sepia: 1 }` 元素 filter 颜色到 100% |
61 | | hueRotate | `{ hueRotate: '90deg' }` 元素 filter 色相盘旋转 90 度 |
62 | | invert | `{ invert: 1 }` 元素 filter 色值反相到 100% |
63 | | brightness | `{ brightness: 2 }` 元素 filter 亮度到 200% |
64 | | contrast | `{ contrast: 2 }` 对比度到 200% |
65 | | saturate | `{ saturate: 2 }` 饱和度到 200% |
66 | | blur | `{ blur: '20px' }` 模糊到 20px |
67 |
68 | > 以上为 tween-one 里的动画可支持参数,如有其它样式可动画或以上有误,烦请 PR 来修改。。
69 |
70 |
--------------------------------------------------------------------------------
/assets/index.less:
--------------------------------------------------------------------------------
1 | @zero: 0;
--------------------------------------------------------------------------------
/docs/changeLog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: changelog
3 | order: 2
4 | ---
5 |
6 | # History
7 |
8 | ---
9 |
10 | ## 3.0.0-beta.x
11 |
12 | - hooks 重构 rc-tween-one;
13 | - 拆离动画库与组件, 动画库 https://www.npmjs.com/package/tween-one
14 |
15 | #### API 变更
16 |
17 | - 删除 `currentRef`, hooks 如果 component 是组件,ref 返回为组件的 ref;
18 | - 增加对 hooks 下的 React.fragment 的支持。
19 | - 增加全局动画播放时的 `delay`;
20 | - 删除 `reverseDelay`;
21 | - 增加 `repeatDelay`;
22 | - `attr` 改为 `boolean` 类型;
23 | - 更新 `onChange` 和回调,cb: { moment, targets, index, mode, ratio, vars, index, repeat }
24 | - 新增 `onChangeTimeline`, cb: { mode, targets, vars, moment, totalTime, repeat }
25 | - 其它回调如 `onStart`、`onUpdate`、`onComplete` 等的 cb 的 target 全部更新为 targets;
26 | - 删除 `BezierPlugin`,合进 PathMotionPlugin;;
27 | - `PathMotionPlugin` 更改用法,使用 `PathMotion: { path, center, x, y, rotate }`, 详细参考 pathMotion demo;
28 | - `SvgMorph` 依赖更改为使用 `flubber`;
29 | - 滤境使用,改成 `import { Plugins } from 'rc-tween-one'; Plugins.push()`, 保留 `TweenOne.plugins.push()`;
30 | - 删除 `TweenOne.easing.path(path)` 使用,直接用 `ease: 'M0,0L100,100'`;
31 | - 增加动画区域播放 `regionStartTime`, `regionEndTime`;
32 | - 增加对背景渐变的动画支持,`backgroundImage: 'linear-gradient(to left, #000fff 0%, red 20%, #fff000 100%)'`
33 | - TweenOneGroup 使用改成 `import TweenOneGroup from 'rc-tween-one/lib/TweenOneGroup';`
34 | ---
35 |
36 | ## 2.2.0
37 |
38 | 1. resetStyleBool 改名成 resetStyle;
39 | 2. TweenOne 删除 updateReStart, 现在默认是。
40 | 3. Group 重构动画逻辑,以队列形式切换,如果在动画时做切换,需将动画完成后再执切换动画。
41 | 4. Group 增加 exclusive, 在队列动画时强行执行切换动画。
42 |
43 | ## 2.0.0
44 |
45 | add repeat and yoyo to tag;
46 |
47 | ## 1.8.0
48 |
49 | add children plugin.
50 |
51 | ## 0.4
52 |
53 | 1. filter 拆分为 'grayScale', 'sepia', 'hueRotate', 'invert', 'brightness', 'contrast', 'blur', 与 transform 一致;
54 |
55 | 2. 忧化时间轴
56 |
57 | 3. 忧化 yoyo 在数组最后个卡顿的问题。
58 |
--------------------------------------------------------------------------------
/docs/demo/animParam.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: anim-params
3 | order: 2
4 | ---
5 |
6 | ## anim-params
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/attr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:attr
3 | order: 4
4 | ---
5 |
6 | ## attr
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/blurFilter.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: filter
3 | order: 3
4 | ---
5 |
6 | ## css filter
7 |
8 | css filter animate
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/demo/component.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:component
3 | order: 4
4 | ---
5 |
6 | ## component
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/componentFragment.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:componentFragment
3 | order: 4
4 | ---
5 |
6 | ## component
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/control.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: control
3 | order: 1
4 | ---
5 |
6 | ## control
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/easingPath.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:easing-path
3 | order: 4
4 | ---
5 |
6 | ## easingPath
7 |
8 | ease path
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/demo/followMouse.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: follow-mouse
3 | order: 5
4 | ---
5 |
6 | ## follow mouse
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/group.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: TweenOneGroup
3 | order: 8
4 | ---
5 |
6 | ### appear = false
7 |
8 | ```jsx
9 | import TweenOneGroup from 'rc-tween-one/es/TweenOneGroup';
10 | import React from 'react';
11 | import { Button } from 'antd';
12 | import 'antd/dist/antd.css';
13 |
14 | const imgArray = [
15 | 'https://os.alipayobjects.com/rmsportal/IhCNTqPpLeTNnwr.jpg',
16 | 'https://os.alipayobjects.com/rmsportal/uaQVvDrCwryVlbb.jpg',
17 | ];
18 | export default () => {
19 | const [int, setInt] = React.useState(0);
20 | return (
21 |
22 |
34 |
35 |
36 |

37 |
38 |
39 |
40 | );
41 | };
42 | ```
43 |
44 | ## basic
45 |
46 |
47 |
48 | ### exclusive = true
49 |
50 | ```jsx
51 | import TweenOneGroup from 'rc-tween-one/es/TweenOneGroup';
52 | import React from 'react';
53 | import { Button } from 'antd';
54 | import 'antd/dist/antd.css';
55 |
56 | const imgArray = [
57 | 'https://os.alipayobjects.com/rmsportal/IhCNTqPpLeTNnwr.jpg',
58 | 'https://os.alipayobjects.com/rmsportal/uaQVvDrCwryVlbb.jpg',
59 | ];
60 | export default () => {
61 | const [int, setInt] = React.useState(0);
62 | return (
63 |
64 |
76 |
77 |
78 |

79 |
80 |
81 |
82 | );
83 | };
84 | ```
85 |
86 | ### children change
87 |
88 | ```jsx
89 | import TweenOneGroup from 'rc-tween-one/es/TweenOneGroup';
90 | import React from 'react';
91 | import { Button } from 'antd';
92 | import 'antd/dist/antd.css';
93 |
94 | const imgArray = [
95 | 'https://os.alipayobjects.com/rmsportal/IhCNTqPpLeTNnwr.jpg',
96 | 'https://os.alipayobjects.com/rmsportal/uaQVvDrCwryVlbb.jpg',
97 | ];
98 | export default () => {
99 | const [children, setChildren] = React.useState(
100 | [
101 |

102 |
]
103 | )
104 | return (
105 |
106 |
118 |
127 |
128 | {children}
129 |
130 |
131 | );
132 | };
133 | ```
134 |
135 | 出场交叉样式
136 |
137 | ```css
138 | .demo-group .tween-one-leaving {
139 | position: absolute;
140 | top: 0;
141 | left: 0;
142 | }
143 | ````
144 |
145 |
152 |
--------------------------------------------------------------------------------
/docs/demo/group:useInTable.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useInTable
3 | order: 8
4 | ---
5 |
6 | ## use in table
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/plugin-gradient.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: plugin:gradient
3 | order: 6
4 | ---
5 |
6 | # gradient
7 |
8 | ```jsx
9 | import React from 'react';
10 | import TweenOne from 'rc-tween-one';
11 |
12 | export default () => {
13 | return (
14 |
15 |
24 |
33 |
34 | );
35 | };
36 | ```
37 |
38 | ### 背景渐变使用示例
39 |
40 | ```js
41 |
46 | ```
47 |
48 | > 注意:渐变的类型(type)、渐变的范围(extent-keyword)、颜色位置的单位请保持一致,否则以 animation 里的优先;
49 |
50 | > 渐变色上面尽量加位置单位,示例 `linear-gradient(to left, #000fff 0%, red 20%, #fff000 100%)`
51 |
52 |
66 |
--------------------------------------------------------------------------------
/docs/demo/plugin-number-dance.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: plugin:number-dance
3 | order: 7
4 | ---
5 |
6 | ## number-dance
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/plugin-path-motion.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: plugin:path-motion
3 | order: 7
4 | ---
5 |
6 | ## path-motion
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/plugin-svg-draw.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: plugin:svg-draw
3 | order: 7
4 | ---
5 |
6 | ## svg-draw
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/plugin-svg-morph.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: plugin:svg-morph
3 | order: 7
4 | ---
5 |
6 | ## svg-morph
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/resetStyle.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:resetStyle
3 | order: 4
4 | ---
5 |
6 | ## type use
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/simple.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: simple
3 | order: 0
4 | ---
5 |
6 | ## basic
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/type.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: api:type
3 | order: 4
4 | ---
5 |
6 | ## type use
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/examples/animParam.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import { Form, InputNumber, Input, Button, Checkbox, Select, AutoComplete } from 'antd';
4 | import 'antd/dist/antd.css';
5 |
6 | const { Option } = Select;
7 |
8 | const ValueComp = (props: any) => {
9 | const { value, onChange } = props;
10 | const [name, setName] = React.useState(value.name || 'x');
11 | const [param, setParam] = React.useState(parseFloat(value.value) || 300);
12 | const [uint, setUint] = React.useState(value.uint || 'px');
13 |
14 | const onNameChange = (e: any) => {
15 | setName(e);
16 | const v: any = {
17 | name: e,
18 | };
19 | switch (e) {
20 | case 'x':
21 | case 'y':
22 | case 'margin':
23 | case 'left':
24 | setParam(300);
25 | setUint('px');
26 | v.value = '300px';
27 | break;
28 | case 'color':
29 | v.value = '#fff000';
30 | setParam(v.value);
31 | setUint(null);
32 | break;
33 | case 'textShadow':
34 | v.value = '5px 5px 5px rgba(0,0,0,0.3)';
35 | setParam(v.value);
36 | setUint(null);
37 | break;
38 | default:
39 | break;
40 | }
41 | onChange(v);
42 | };
43 | const onValueChange = (e: any) => {
44 | setParam(e);
45 | onChange({ ...value, value: e });
46 | };
47 | const onUintChange = (e: any) => {
48 | setUint(e);
49 | onChange({ ...value, uint: e });
50 | };
51 | return (
52 |
53 |
61 |
62 | {uint && (
63 |
68 | )}
69 |
70 | );
71 | };
72 |
73 | export default () => {
74 | const [anim, setAnim] = React.useState();
75 | const [paused, setPaused] = React.useState(true);
76 |
77 | const onFinish = (values: any) => {
78 | console.log('Received values from form: ', values);
79 | setPaused(false);
80 | const { value, ...v } = values;
81 | setAnim({
82 | [value.name]: `${value.value}${value.uint || ''}`,
83 | ...v,
84 | });
85 | };
86 |
87 | return (
88 |
89 |
90 | 执行动效
91 |
92 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | yoyo
110 |
111 |
112 |
115 |
116 |
117 |
118 | 注:
119 |
120 | 1. 更多动画参数请参考{' '}
121 |
122 | ant motion 动画参数
123 |
124 |
125 | 2. 动画值可以为 '+=300' 或 '-=300',表示当前值增加或减少。
126 |
127 | 3. repeat 为 n + 1 次,如 2 则播放 3 次动画, -1 时为无限循环
128 |
129 | 4. 参数不变,开始动画无效,请保证每次动画的参数
130 |
131 |
132 | );
133 | };
134 |
--------------------------------------------------------------------------------
/docs/examples/attr.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | export default () => {
5 | return (
6 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/docs/examples/blurFilter.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | export default function Demo() {
5 | return (
6 |
7 |
8 | filter 里的滤镜,'grayScale', 'sepia',
9 | 'hueRotate', 'invert', 'brightness',
10 | 'contrast', 'blur'
11 |
12 |
16 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/docs/examples/component.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 |
5 | import { Menu } from 'antd';
6 | import {
7 | PieChartOutlined,
8 | DesktopOutlined,
9 | ContainerOutlined,
10 | MailOutlined,
11 | } from '@ant-design/icons';
12 | import 'antd/dist/antd.css';
13 |
14 | const { SubMenu, Item } = Menu;
15 |
16 | const T = React.forwardRef((_, ref: any) => Function Component
);
17 | class C extends React.Component {
18 | render() {
19 | return Class Component
;
20 | }
21 | }
22 |
23 | const anim = { x: 300 };
24 |
25 | export default () => {
26 | const dom = React.useRef();
27 |
28 | React.useEffect(() => {
29 | console.log('useRef:', dom);
30 | });
31 |
32 | return (
33 |
34 |
{
36 | // It can also be used React.useRef();
37 | console.log('function component HTMLElement:', c);
38 | }}
39 | component={T}
40 | animation={anim}
41 | />
42 |
43 | {
45 | // It can also be used React.useRef();
46 | console.log('class component:', c, ReactDOM.findDOMNode(c));
47 | }}
48 | component={C}
49 | animation={anim}
50 | />
51 |
52 | UseRef Component is string
53 |
54 | ant design menu
55 |
56 |
67 | }}
71 | key="1"
72 | >
73 | Option 1
74 |
75 | }>
76 | Option 2
77 |
78 | }>
79 | Option 3
80 |
81 | , title: 'Navigation One' }}
86 | >
87 |
Option 5
88 | Option 6
89 | Option 7
90 | Option 8
91 |
92 |
93 |
94 |
95 | );
96 | };
97 |
--------------------------------------------------------------------------------
/docs/examples/componentFragment.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | class D extends React.Component {
5 | render() {
6 | return d
;
7 | }
8 | }
9 |
10 | const C = React.forwardRef((_, ref: any) => {
11 | return c
;
12 | });
13 |
14 | const Comp = React.forwardRef((_, ref: any) => {
15 | const domRef = React.useRef([]);
16 | const refFunc = (c: any) => {
17 | if (domRef.current.some((a) => a === c)) {
18 | return;
19 | }
20 | domRef.current.push(c);
21 | ref(domRef.current);
22 | };
23 | return (
24 | <>
25 | a
26 |
27 |
28 | >
29 | );
30 | });
31 |
32 | export default () => {
33 | return (
34 | {
36 | console.log(c);
37 | }}
38 | animation={{ x: 300 }}
39 | style={{ position: 'relative' }}
40 | component={Comp}
41 | >
42 | 执行动效
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/docs/examples/control.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import { Button, Space } from 'antd';
4 | import 'antd/dist/antd.css';
5 |
6 | const animation = [{ translateX: '500px', duration: 1000 }, { y: 100 }];
7 | export default () => {
8 | const [paused, setPaused] = React.useState(true);
9 | const [reverse, setReverse] = React.useState(false);
10 | const [moment, setMoment] = React.useState(0);
11 |
12 | return (
13 |
14 |
15 |
{ console.log(e)}}
21 | style={{ opacity: 1, width: 100, transform: 'translate(50px,30px)' }}
22 | >
23 | 执行动效
24 |
25 |
26 |
27 |
35 |
42 |
50 |
60 |
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/docs/examples/easingPath.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | const p1 = 'M0,100 L25,100 C34,20 40,0 100,0';
5 | const p =
6 | 'M0,100 C5,120 25,130 25,100 C30,60 40,75 58,90 C69,98.5 83,99.5 100,100';
7 |
8 | const anim = [
9 | {
10 | repeatDelay: 1000,
11 | duration: 1000,
12 | scaleX: 0,
13 | scaleY: 2,
14 | repeat: -1,
15 | yoyo: true,
16 | ease: p,
17 | },
18 | {
19 | repeatDelay: 1000,
20 | duration: 1000,
21 | y: 0,
22 | appearTo: 0,
23 | repeat: -1,
24 | yoyo: true,
25 | ease: p1,
26 | },
27 | ];
28 | export default () => {
29 | return (
30 |
31 |
43 |
51 |
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/docs/examples/followMouse.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | export default () => {
5 | const [animation, setAnim] = React.useState();
6 | const mouseMove = (e: any) => {
7 | const x = e.clientX;
8 | setAnim({ x, duration: 1000, ease: 'easeOutQuad' });
9 | };
10 | React.useEffect(() => {
11 | window.addEventListener('mousemove', mouseMove);
12 | }, [])
13 | return (
14 |
15 | 执行动效
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/docs/examples/group.tsx:
--------------------------------------------------------------------------------
1 | import TweenOneGroup from 'rc-tween-one/es/TweenOneGroup';
2 | import React from 'react';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import '../../assets/index.less';
6 |
7 | const imgArray = [
8 | 'https://os.alipayobjects.com/rmsportal/IhCNTqPpLeTNnwr.jpg',
9 | 'https://os.alipayobjects.com/rmsportal/uaQVvDrCwryVlbb.jpg',
10 | ];
11 | export default () => {
12 | const [int, setInt] = React.useState(0);
13 | return (
14 |
15 |
27 |
28 |
29 |

30 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/docs/examples/numberDance.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import ChildrenPlugin from 'rc-tween-one/es/plugin/ChildrenPlugin';
4 |
5 |
6 | Tween.plugins.push(ChildrenPlugin);
7 | export default function Demo() {
8 | return (
9 |
10 |
子级的数值变化的动画 - children plugin
11 |
12 | 基本数字:
13 |
14 |
15 |
16 | 设置开始数字:
17 | 9990
18 |
19 |
20 | 只取整数:
21 |
22 |
23 |
24 | 基本数字, 小数后取两位, float length 2:
25 |
26 |
27 |
28 |
格式化钱符:
29 |
30 | ¥
31 |
37 | 20,000.12
38 |
39 |
40 |
41 |
42 |
自定义钱符格式:
43 |
44 | ¥
45 |
55 | 20.000,12
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/docs/examples/pathMotion.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import PathPlugin from 'rc-tween-one/es/plugin/PathMotionPlugin';
4 |
5 | Tween.plugins.push(PathPlugin);
6 |
7 | const array = [
8 | { x: 50, y: 50 },
9 | { x: 200, y: 250 },
10 | { x: 350, y: 50 },
11 | { x: 500, y: 250 },
12 | { x: 650, y: 50 } /*
13 | { x: 800, y: 250 },
14 | { x: 950, y: 50 }, */,
15 | ];
16 |
17 | export default function Demo() {
18 | const p = `M50.952,85.619C31.729,84.841,23.557,73.62,24.095,42.952
19 | c0.381-21.714,6.667-33.714,30.286-34.476
20 | c36.572-1.18,59.81,77.714,102.667,76.381c30.108-0.937,34.268-32.381,34.095-41.714
21 | C190.762,22.571,180.493,6.786,159.524,6C113.81,4.286,98,87.524,50.952,85.619z`;
22 |
23 | const p2 = 'M0,0,L100, 0L100, 100L0, 100Z';
24 | return (
25 |
26 |
27 |
42 | a
43 |
44 |
59 | a
60 |
61 |
65 |
66 |
75 | {array.map((item, i) => {
76 | return (
77 |
90 | );
91 | })}
92 |
c
111 |
112 |
113 | );
114 | }
115 |
--------------------------------------------------------------------------------
/docs/examples/resetStyle.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React, { useEffect } from 'react';
3 | import 'antd/dist/antd.css';
4 |
5 | export default () => {
6 | const [anim, setAnim] = React.useState({
7 | x: 500,
8 | duration: 1000,
9 | });
10 | useEffect(() => {
11 | setTimeout(() => {
12 | setAnim({
13 | y: 100,
14 | duration: 1000,
15 | });
16 | }, 500);
17 | }, []);
18 |
19 | return (
20 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/docs/examples/simple.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 |
4 | export default () => {
5 | const bbb = (e: any) => {
6 | console.log(e); // eslint-disable-line no-console
7 | };
8 | return (
9 |
14 | 执行动效
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/docs/examples/svg.tsx:
--------------------------------------------------------------------------------
1 | import Tween, { Plugins } from 'rc-tween-one';
2 | import React from 'react';
3 | import SvgMorphPlugin from 'rc-tween-one/es/plugin/SvgMorphPlugin';
4 |
5 | // Tween.plugins.push(SvgMorphPlugin);
6 | Plugins.push(SvgMorphPlugin);
7 |
8 | export default function Demo() {
9 | return (
10 |
11 |
24 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/docs/examples/svgDraw.tsx:
--------------------------------------------------------------------------------
1 | import Tween, { Plugins } from 'rc-tween-one';
2 | import React from 'react';
3 | import SvgDrawPlugin from 'rc-tween-one/es/plugin/SvgDrawPlugin';
4 |
5 | Plugins.push(SvgDrawPlugin);
6 |
7 | const dataStartArr = ['100%', '30 450', '50% 50%', '30% 400', '50 30%', 0];
8 | let i = 0;
9 |
10 | export default () => {
11 | const [tweenData, setTweenData] = React.useState('50 30%');
12 | const onClick = () => {
13 | setTweenData(dataStartArr[i]);
14 | i++;
15 | i = i >= dataStartArr.length ? 0 : i;
16 | };
17 | return (
18 |
19 |
20 |
当前参数:{tweenData}
21 |
87 |
88 | );
89 | };
90 |
--------------------------------------------------------------------------------
/docs/examples/type.tsx:
--------------------------------------------------------------------------------
1 | import Tween from 'rc-tween-one';
2 | import React from 'react';
3 | import { Button, Space } from 'antd';
4 | import 'antd/dist/antd.css';
5 |
6 | export default () => {
7 | const [anim, setAnim] = React.useState({
8 | x: 500,
9 | duration: 1000,
10 | });
11 | const [paused, setPaused] = React.useState(true);
12 |
13 | return (
14 |
15 |
16 |
{ console.log(e)}}
20 | style={{ opacity: 1, width: 100, transform: 'translate(50px,30px)' }}
21 | >
22 | 执行动效
23 |
24 |
25 |
播放类型(type):
26 |
27 |
38 |
50 |
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/docs/examples/useInTable.less:
--------------------------------------------------------------------------------
1 | .table-enter-leave-demo-nav {
2 | height: 50px;
3 | background: #080B20;
4 | line-height: 50px;
5 | }
6 |
7 | .table-enter-leave-demo-nav span {
8 | margin-left: 20px;
9 | }
10 |
11 | .table-enter-leave-demo-nav img:first-child {
12 | filter: grayscale(1) brightness(3);
13 | }
14 |
15 | .table-enter-leave-demo-nav img:last-child {
16 | margin-left: 10px;
17 | }
18 |
19 | .table-enter-leave-demo-list {
20 | width: 20%;
21 | height: calc(100% - 82px);
22 | display: inline-block;
23 | background: #171C3E;
24 | }
25 |
26 | .table-enter-leave-demo-list ul li {
27 | width: 80%;
28 | height: 15px;
29 | background: #23274A;
30 | margin: 20px auto;
31 | border-radius: 4px;
32 | }
33 |
34 | .table-enter-leave-demo-table-wrapper {
35 | width: 80%;
36 | display: inline-block;
37 | overflow: auto;
38 | height: calc(100% - 82px);
39 | }
40 |
41 | .table-enter-leave-demo-action-bar {
42 | margin: 20px;
43 | background: #fff;
44 | line-height: 40px;
45 | height: 40px;
46 | position: relative;
47 | border-radius: 4px;
48 | }
49 |
50 | .table-enter-leave-demo-action-bar:before, .table-enter-leave-demo-action-bar:after {
51 | content: '';
52 | display: inline-block;
53 | background: #E5E5E5;
54 | vertical-align: middle;
55 | }
56 |
57 | .table-enter-leave-demo-action-bar:before {
58 | width: 20px;
59 | height: 20px;
60 | border-radius: 100%;
61 | margin-left: 10px;
62 | }
63 |
64 | .table-enter-leave-demo-action-bar:after {
65 | position: absolute;
66 | width: 100px;
67 | height: 10px;
68 | top: 0;
69 | bottom: 0;
70 | margin: auto;
71 | left: 40px;
72 | }
73 |
74 | .table-enter-leave-demo-action-bar button {
75 | position: absolute;
76 | right: 10px;
77 | top: 0;
78 | bottom: 0;
79 | margin: auto;
80 | width: 80px;
81 | border-radius: 4px;
82 | line-height: 1;
83 | }
84 |
85 | .table-enter-leave-demo-delete {
86 | color: #019BF0;
87 | cursor: pointer;
88 | }
89 |
90 | .table-enter-leave-demo-table {
91 | margin: 0 20px;
92 | overflow: hidden;
93 | }
94 | .table-enter-leave-demo-table .ant-table-thead > tr > th {
95 | display: inline-block;
96 | padding: 10px 8px;
97 | background: #e5e5e5;
98 | }
99 | .table-enter-leave-demo-table .ant-table-tbody > tr {
100 | background: #fff;
101 | /* 不能用
102 | border-bottom: 1px solid #e9e9e9;
103 | */
104 | }
105 | .table-enter-leave-demo-table .ant-table-tbody > tr > td {
106 | display: inline-block;
107 | padding: 10px 8px;
108 | vertical-align: bottom;
109 | }
110 |
111 | .table-enter-leave-demo-table .ant-table-thead > tr,
112 | .table-enter-leave-demo-table .ant-table-tbody > tr,
113 | .table-enter-leave-demo-table .ant-table-tbody {
114 | display: block;
115 | transition: none;
116 | overflow: hidden;
117 | }
118 |
119 | .table-enter-leave-demo-table .ant-table-thead > tr > th:nth-child(1),
120 | .table-enter-leave-demo-table .ant-table-tbody > tr > td:nth-child(1) {
121 | width: 25%;
122 | }
123 | .table-enter-leave-demo-table .ant-table-thead > tr > th:nth-child(2),
124 | .table-enter-leave-demo-table .ant-table-tbody > tr > td:nth-child(2) {
125 | width: 15%;
126 | }
127 | .table-enter-leave-demo-table .ant-table-thead > tr > th:nth-child(3),
128 | .table-enter-leave-demo-table .ant-table-tbody > tr > td:nth-child(3) {
129 | width: 40%;
130 | }
131 | .table-enter-leave-demo-table .ant-table-thead > tr > th:nth-child(4),
132 | .table-enter-leave-demo-table .ant-table-tbody > tr > td:nth-child(4) {
133 | width: 20%;
134 | }
135 |
136 | @media screen and (max-width: 414px) {
137 | .table-enter-leave-demo {
138 | transform: scale(0.65) translateX(12px);
139 | transform-origin: left center;
140 | }
141 | }
142 |
143 | @media screen and (max-width: 375px) {
144 | .table-enter-leave-demo {
145 | transform: scale(0.6) translateX(7px);
146 | }
147 | }
148 |
149 | @media screen and (max-width: 320px) {
150 | .table-enter-leave-demo {
151 | transform: scale(0.5) translateX(12px);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/docs/examples/useInTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Table from 'antd/lib/table';
3 | import Button from 'antd/lib/button';
4 | import TweenOneGroup from 'rc-tween-one/es/TweenOneGroup';
5 | import 'antd/dist/antd.css';
6 | import './useInTable.less';
7 |
8 | const TableContext = React.createContext<{ enter: any; leave: any }>({ enter: null, leave: null });
9 |
10 | const data: { key: string; name: string; age: number; address: string; tags?: string[] }[] = [
11 | {
12 | key: '1',
13 | name: 'John Brown',
14 | age: 32,
15 | address: 'New York No. 1 Lake Park',
16 | tags: ['nice', 'developer'],
17 | },
18 | {
19 | key: '2',
20 | name: 'Jim Green',
21 | age: 42,
22 | address: 'London No. 1 Lake Park',
23 | tags: ['loser'],
24 | },
25 | {
26 | key: '3',
27 | name: 'Joe Black',
28 | age: 32,
29 | address: 'Sidney No. 1 Lake Park',
30 | tags: ['cool', 'teacher'],
31 | },
32 | ];
33 |
34 | // 动画标签,页面切换时改用 context 传递参数;
35 | const animTag = ($props: any) => {
36 | return (
37 |
38 | {({ enter, leave }) => {
39 | console.log(enter);
40 | const children = React.Children.toArray($props.children);
41 |
42 | return (
43 |
51 | {children.map((item: any) => (
52 |
53 | {item}
54 |
55 | ))}
56 |
57 | );
58 | }}
59 |
60 | );
61 | };
62 |
63 | export default ({ className = 'table-enter-leave-demo' }) => {
64 | const [isPageTween, setPageTween] = useState(false);
65 | const [tableData, setTableData] = useState(data);
66 |
67 | const onEnd = () => {
68 | setPageTween(false);
69 | };
70 |
71 | const rmStyle = (e: any) => {
72 | const { targets } = e;
73 | // callback in render before
74 | setTimeout(() => {
75 | targets.style = '';
76 | });
77 | };
78 |
79 | const onAdd = () => {
80 | const newData = [...tableData];
81 | const i = Math.round(Math.random() * (tableData.length - 1));
82 | newData.unshift({
83 | key: `${Date.now()}`,
84 | name: tableData[i].name,
85 | age: tableData[i].age,
86 | address: tableData[i].address,
87 | });
88 | setTableData(newData);
89 | };
90 |
91 | const onDelete = (key: string, e: any) => {
92 | e.preventDefault();
93 | const newData = tableData.filter((item) => item.key !== key);
94 | setTableData(newData);
95 | setPageTween(false);
96 | };
97 |
98 | const pageChange = () => {
99 | setPageTween(true);
100 | };
101 |
102 | const columns = [
103 | { title: 'Name', dataIndex: 'name', key: 'name' },
104 | { title: 'Age', dataIndex: 'age', key: 'age' },
105 | { title: 'Address', dataIndex: 'address', key: 'address' },
106 | {
107 | title: 'Action',
108 | dataIndex: '',
109 | key: 'x',
110 | render: (text: string, record: any) => (
111 | {
114 | onDelete(record.key, e);
115 | }}
116 | >
117 | Delete
118 |
119 | ),
120 | },
121 | ];
122 | const enterAnim = [
123 | {
124 | opacity: 0,
125 | x: 30,
126 | // backgroundColor: '#fffeee', // tbody no backgroundColor
127 | duration: 0,
128 | },
129 | {
130 | height: 0,
131 | duration: 200,
132 | type: 'from',
133 | delay: 250,
134 | ease: 'easeOutQuad',
135 | onComplete: onEnd,
136 | },
137 | {
138 | opacity: 1,
139 | x: 0,
140 | duration: 250,
141 | ease: 'easeOutQuad',
142 | onComplete: rmStyle,
143 | },
144 | // { delay: 1000, backgroundColor: '#fff', onComplete: rmStyle },
145 | ];
146 | const pageEnterAnim = [
147 | {
148 | opacity: 0,
149 | duration: 0,
150 | },
151 | {
152 | height: 0,
153 | duration: 150,
154 | type: 'from',
155 | delay: 150,
156 | ease: 'easeOutQuad',
157 | onComplete: onEnd,
158 | },
159 | {
160 | opacity: 1,
161 | duration: 150,
162 | ease: 'easeOutQuad',
163 | onComplete: rmStyle,
164 | },
165 | ];
166 | const leaveAnim = [
167 | { duration: 250, opacity: 0 },
168 | { height: 0, duration: 200, ease: 'easeOutQuad' },
169 | ];
170 | const pageLeaveAnim = [
171 | { duration: 150, opacity: 0 },
172 | { height: 0, duration: 150, ease: 'easeOutQuad' },
173 | ];
174 |
175 | return (
176 |
177 |
178 |
181 |
182 |
188 |
196 |
197 |
198 | );
199 | };
200 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: rc-tween-one
3 | order: 0
4 | ---
5 |
6 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "name": "rc-tween-one",
4 | "builds": [
5 | {
6 | "src": "package.json",
7 | "use": "@now/static-build",
8 | "config": { "distDir": ".doc" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-tween-one",
3 | "version": "3.0.6",
4 | "description": "tween-one anim component for react",
5 | "typings": "es/index.d.ts",
6 | "engines": {
7 | "node": ">=8.x"
8 | },
9 | "keywords": [
10 | "react",
11 | "react-component",
12 | "component",
13 | "react-tween-one",
14 | "tween",
15 | "react-tween",
16 | "tween-one",
17 | "animation",
18 | "animate",
19 | "rc-animation",
20 | "react-animation",
21 | "rc-animate",
22 | "react-animate",
23 | "motion",
24 | "rc-motion",
25 | "ant-motion"
26 | ],
27 | "homepage": "https://github.com/react-component/tween-one",
28 | "author": "155259966@qq.com",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/react-component/tween-one.git"
32 | },
33 | "bugs": {
34 | "url": "https://github.com/react-component/tween-one/issues"
35 | },
36 | "files": [
37 | "es",
38 | "lib",
39 | "assets/*.css",
40 | "assets/*.less"
41 | ],
42 | "licenses": "MIT",
43 | "main": "./lib/index",
44 | "module": "./es/index",
45 | "sideEffects": false,
46 | "scripts": {
47 | "start": "dumi dev",
48 | "docs:build": "dumi build",
49 | "docs:deploy": "gh-pages -d docs-dist",
50 | "compile": "father-build",
51 | "deploy": "npm run docs:build && npm run docs:deploy",
52 | "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
53 | "test": "umi-test test",
54 | "test:coverage": "umi-test --coverage",
55 | "prepublishOnly": "npm run compile && np --tag=beta --no-cleanup --yolo --no-publish --any-branch",
56 | "lint": "eslint src/ --fix --ext .tsx,.ts",
57 | "lint:tsc": "tsc -p tsconfig.json --noEmit",
58 | "now-build": "npm run docs:build"
59 | },
60 | "devDependencies": {
61 | "@ant-design/icons": "^4.3.0",
62 | "@types/enzyme": "^3.10.5",
63 | "@types/jest": "^25.2.1",
64 | "@types/lodash": "^4.14.135",
65 | "@types/react": "^16.8.19",
66 | "@types/react-dom": "^16.8.4",
67 | "@umijs/test": "^3.2.28",
68 | "antd": "^4.8.4",
69 | "dumi": "^1.1.0-rc.1",
70 | "enzyme": "^3.3.0",
71 | "enzyme-adapter-react-16": "^1.0.2",
72 | "enzyme-to-json": "^3.4.0",
73 | "eslint": "^7.14.0",
74 | "father": "^2.22.6",
75 | "father-build": "^1.18.6",
76 | "gh-pages": "^3.1.0",
77 | "husky": "^4.3.0",
78 | "np": "^6.0.3",
79 | "prettier": "^2.1.2",
80 | "react": "^16.9.0",
81 | "react-dom": "^16.9.0",
82 | "regenerator-runtime": "^0.13.7",
83 | "typescript": "^4.0.2"
84 | },
85 | "peerDependencies": {
86 | "react": ">=16.9.0",
87 | "react-dom": ">=16.9.0"
88 | },
89 | "dependencies": {
90 | "@babel/runtime": "^7.11.1",
91 | "style-utils": "^0.3.4",
92 | "tween-one": "^1.0.50"
93 | },
94 | "husky": {
95 | "hooks": {
96 | "pre-commit": [
97 | "npm run lint"
98 | ]
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/TweenOne.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, createElement, useEffect } from 'react';
2 | import { findDOMNode } from 'react-dom';
3 | import type { Tween } from 'tween-one';
4 | import TweenOneJS from 'tween-one';
5 | import { toStyleUpperCase, stylesToCss } from 'style-utils';
6 |
7 | import type { IAnimProps, IAnimObject, TweenOneRef } from './type';
8 | import { objectEqual, dataToArray } from './utils';
9 | import { useIsomorphicLayoutEffect } from './utils/common';
10 |
11 | const TweenOne: TweenOneRef = React.forwardRef(
12 | (
13 | {
14 | component = 'div',
15 | componentProps,
16 | animation,
17 | attr,
18 | paused,
19 | reverse,
20 | repeat,
21 | repeatDelay,
22 | yoyo,
23 | moment,
24 | onChange,
25 | onChangeTimeline,
26 | resetStyle,
27 | killPrevAnim = true,
28 | ...props
29 | },
30 | ref,
31 | ) => {
32 | const { children, className, style = {} } = props || {};
33 |
34 | const domRef = useRef();
35 | const prevAnim = useRef();
36 | const animRef = useRef();
37 | const commonFunc = (
38 | key: 'paused' | 'moment' | 'reverse',
39 | value: boolean | number | undefined,
40 | ) => {
41 | const tween: Tween = animRef.current!;
42 | if (tween) {
43 | if (key === 'moment') {
44 | if (typeof value === 'number') {
45 | tween.goto(value, paused);
46 | }
47 | return;
48 | }
49 | tween[key] = !!value;
50 | }
51 | };
52 | useIsomorphicLayoutEffect(() => {
53 | commonFunc('paused', paused);
54 | }, [paused]);
55 | // yoyo, moment, reverse, repeat, repeatDelay
56 | useIsomorphicLayoutEffect(() => {
57 | commonFunc('moment', moment);
58 | }, [moment]);
59 |
60 | useIsomorphicLayoutEffect(() => {
61 | commonFunc('reverse', reverse);
62 | }, [reverse]);
63 | useIsomorphicLayoutEffect(() => {
64 | if (!domRef.current) {
65 | return console.warn('Warning: TweenOne domRef is error.');
66 | }
67 | // 动画写在标签上,手动对比;
68 | if (!objectEqual(animation, prevAnim.current)) {
69 | const doms: any[] = dataToArray(domRef.current)
70 | .map((item) =>
71 | item instanceof Element || !(item instanceof React.Component)
72 | ? item
73 | : findDOMNode(item),
74 | )
75 | .filter((item, i) => {
76 | if (!(item instanceof Element)) {
77 | console.warn(`Warning: TweenOne tag[${i}] is not dom.`);
78 | return false;
79 | }
80 | return item;
81 | });
82 |
83 | if (animRef.current && killPrevAnim) {
84 | animRef.current.kill();
85 | }
86 | if (resetStyle && animRef.current) {
87 | const s = !component ? { ...style, ...children.props.style } : style
88 | const styleStr = Object.keys(s)
89 | .map((key: string) => `${toStyleUpperCase(key)}:${stylesToCss(key, s[key])}`)
90 | .join(';');
91 | doms.forEach((item: Element) => {
92 | item.setAttribute('style', styleStr);
93 | // dom.style.cssText = styleStr;
94 | delete item._tweenOneVars; // eslint-disable-line no-underscore-dangle
95 | });
96 | }
97 | animRef.current =
98 | animation &&
99 | TweenOneJS(doms, {
100 | animation,
101 | attr,
102 | yoyo,
103 | moment,
104 | repeat,
105 | reverse,
106 | paused,
107 | repeatDelay,
108 | onChange,
109 | onChangeTimeline,
110 | });
111 | prevAnim.current = animation;
112 | }
113 | }, [animation]);
114 | useEffect(
115 | () => () => {
116 | if (animRef.current && animRef.current.kill) {
117 | animRef.current.kill();
118 | }
119 | },
120 | [],
121 | );
122 |
123 | const refFunc = (c: any) => {
124 | domRef.current = c;
125 | if (ref && 'current' in ref) {
126 | ref.current = c;
127 | } else if (typeof ref === 'function') {
128 | ref(c);
129 | }
130 | };
131 |
132 | if (
133 | !component &&
134 | children &&
135 | typeof children !== 'string' &&
136 | typeof children !== 'boolean' &&
137 | typeof children !== 'number'
138 | ) {
139 | const childrenProps = children.props;
140 | const { style: childStyle, className: childClass = '' } = childrenProps || {};
141 | // 合并 style 与 className。
142 | const newStyle = { ...childStyle, ...style };
143 | const newClassName = className ? `${className} ${childClass}`.trim() : childClass;
144 | return React.cloneElement(children, {
145 | style: newStyle,
146 | ref: refFunc,
147 | className: [...new Set(newClassName.split(/\s+/))].join(' ').trim() || undefined,
148 | });
149 | }
150 | if (!component) {
151 | console.warn('Warning: component is null, children must be ReactElement.');
152 | return children;
153 | }
154 | return createElement(component, {
155 | ref: refFunc,
156 | ...props,
157 | ...componentProps,
158 | });
159 | },
160 | );
161 | TweenOne.isTweenOne = true;
162 | TweenOne.displayName = 'TweenOne';
163 |
164 | export default TweenOne;
165 |
--------------------------------------------------------------------------------
/src/TweenOneGroup.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactElement, ReactText } from 'react';
2 | import { cloneElement } from 'react';
3 | import React, { useRef, useEffect, useState, createElement } from 'react';
4 | import type {
5 | IGroupProps,
6 | IAnimObject,
7 | TweenOneGroupRef,
8 | ITimelineCallBack,
9 | IObject,
10 | } from './type';
11 | import {
12 | getChildrenFromProps,
13 | toArrayChildren,
14 | transformArguments,
15 | mergeChildren,
16 | findChildInChildrenByKey,
17 | } from './utils/group';
18 | import { useIsomorphicLayoutEffect, windowIsUndefined } from './utils/common';
19 |
20 | import TweenOne from './TweenOne';
21 |
22 | const TweenOneGroup: TweenOneGroupRef = React.forwardRef((props, ref) => {
23 | const {
24 | component = 'div',
25 | componentProps = {},
26 | leave: leaveAnim = { x: -50, opacity: 0 },
27 | enter: enterAnim = { x: 50, opacity: 0, type: 'from' },
28 | appear: appearBool = true,
29 | resetStyle = true,
30 | animatingClassName = ['tween-one-entering', 'tween-one-leaving'],
31 | onEnd = () => {},
32 | exclusive = false,
33 | ...tagProps
34 | } = props;
35 | const keysToEnter = useRef([]);
36 | const keysToLeave = useRef([]);
37 | const saveTweenTag = useRef({});
38 | const oneEnter = useRef(false);
39 | const animQueue = useRef([]);
40 | const isTween = useRef({});
41 | const cChild = toArrayChildren(getChildrenFromProps(props));
42 | const currentChildren = useRef(cChild);
43 | const [children, setChild] = useState(cChild);
44 |
45 | const getTweenChild = (child: ReactElement, p = {}) => {
46 | const key: string | number = child.key as string;
47 | saveTweenTag.current[key] = React.createElement(
48 | TweenOne,
49 | {
50 | ...p,
51 | key,
52 | component: null,
53 | },
54 | child,
55 | );
56 | return saveTweenTag.current[key];
57 | };
58 | const setClassName = (name: string, isEnter: boolean) => {
59 | let className = name.replace(animatingClassName[isEnter ? 1 : 0], '').trim();
60 | if (className.indexOf(animatingClassName[isEnter ? 0 : 1]) === -1) {
61 | className = `${className} ${animatingClassName[isEnter ? 0 : 1]}`.trim();
62 | }
63 | return className;
64 | };
65 | const changeChildren = (nextChildren: ReactElement[], currentChild: ReactElement[]) => {
66 | const newChildren: ReactElement[] = mergeChildren(currentChild, nextChildren);
67 | keysToEnter.current = [];
68 | keysToLeave.current = [];
69 | nextChildren.forEach((c) => {
70 | if (!c) {
71 | return;
72 | }
73 | const { key } = c;
74 | const hasPrev = findChildInChildrenByKey(currentChild, key);
75 | // 如果当前 key 已存在 saveTweenTag 里,,刷新 child;
76 | if (key && saveTweenTag.current[key]) {
77 | saveTweenTag.current[key] = React.cloneElement(saveTweenTag.current[key], {}, c);
78 | }
79 | if (!hasPrev && key) {
80 | keysToEnter.current.push(key);
81 | }
82 | });
83 |
84 | currentChild.forEach((c) => {
85 | if (!c) {
86 | return;
87 | }
88 | const { key } = c;
89 | const hasNext = findChildInChildrenByKey(nextChildren, key);
90 | if (!hasNext && key) {
91 | keysToLeave.current.push(key);
92 | delete saveTweenTag.current[key];
93 | }
94 | });
95 | return newChildren;
96 | };
97 |
98 | const reAnimQueue = () => {
99 | if (!Object.keys(isTween.current).length && animQueue.current.length) {
100 | // 取最后一个继续动画;
101 | const child = changeChildren(
102 | animQueue.current[animQueue.current.length - 1],
103 | currentChildren.current,
104 | );
105 | setChild(child);
106 | animQueue.current = [];
107 | }
108 | };
109 |
110 | const onChange = (key: string | number | null, type: string, obj: ITimelineCallBack) => {
111 | const tag = obj.targets as IObject;
112 | const classIsSvg = typeof tag!.className === 'object' && 'baseVal' in tag!.className;
113 | const isEnter = type === 'enter' || type === 'appear';
114 | if (key && obj.mode === 'onTimelineComplete') {
115 | delete isTween.current[key];
116 | if (classIsSvg) {
117 | tag.className.baseVal = tag.className.baseVal
118 | .replace(animatingClassName[isEnter ? 0 : 1], '')
119 | .trim();
120 | } else {
121 | tag.className = tag.className.replace(animatingClassName[isEnter ? 0 : 1], '').trim();
122 | }
123 | if (isEnter) {
124 | keysToEnter.current.splice(keysToEnter.current.indexOf(key), 1);
125 | if (!keysToEnter.current.length) {
126 | // enter 不会触发 did update, 手动触发一次;
127 | reAnimQueue();
128 | }
129 | } else if (type === 'leave') {
130 | keysToLeave.current.splice(keysToLeave.current.indexOf(key), 1);
131 | currentChildren.current = currentChildren.current.filter((child) => key !== child.key);
132 | if (!keysToLeave.current.length) {
133 | const currentChildrenKeys = currentChildren.current.map((item) => item.key);
134 | Object.keys(saveTweenTag.current).forEach(($key) => {
135 | if (currentChildrenKeys.indexOf($key) === -1) {
136 | delete saveTweenTag.current[$key];
137 | }
138 | });
139 |
140 | setChild(currentChildren.current);
141 | }
142 | }
143 | onEnd({ key, type, target: obj.targets as any });
144 | }
145 | };
146 | const getCoverAnimation = (child: ReactElement, i: number, type: string) => {
147 | let animation: IAnimObject = type === 'leave' ? leaveAnim : enterAnim;
148 | if (type === 'appear') {
149 | const appear = transformArguments(appearBool, child.key, i);
150 | animation = (appear && enterAnim) || null;
151 | }
152 | const animate = transformArguments(animation, child.key, i);
153 | const onChangeCb = (obj: ITimelineCallBack) => {
154 | onChange(child.key, type, obj);
155 | };
156 | const className =
157 | type === 'appear' && !appearBool
158 | ? child.props.className
159 | : setClassName(child.props.className || '', type === 'enter' || type === 'appear') ||
160 | undefined;
161 | const p = {
162 | key: child.key,
163 | animation: animate,
164 | onChangeTimeline: onChangeCb,
165 | resetStyle,
166 | className,
167 | };
168 | if (
169 | (child.key && keysToEnter.current.concat(keysToLeave.current).indexOf(child.key) >= 0) ||
170 | (!oneEnter.current && animation)
171 | ) {
172 | if (child.key && !saveTweenTag.current[child.key]) {
173 | isTween.current[child.key] = type;
174 | }
175 | }
176 |
177 | return getTweenChild(child, p);
178 | };
179 | useIsomorphicLayoutEffect(() => {
180 | if (oneEnter.current) {
181 | const nextChild = toArrayChildren(props.children).filter((c) => c);
182 | const currentChild = toArrayChildren(currentChildren.current);
183 | // 如果还在动画,暂存动画队列里,等前一次动画结束后再启动最后次的更新动画
184 | if (Object.keys(isTween.current).length && !exclusive) {
185 | animQueue.current.push(nextChild);
186 | } else {
187 | setChild(changeChildren(nextChild, currentChild));
188 | }
189 | }
190 | }, [props.children]);
191 | useIsomorphicLayoutEffect(() => {
192 | reAnimQueue();
193 | });
194 | useEffect(() => {
195 | oneEnter.current = true;
196 | }, []);
197 |
198 | currentChildren.current = children;
199 |
200 | const childrenToRender = children.map((child: ReactElement, i: number) => {
201 | if (!child || !child.key) {
202 | return child;
203 | }
204 | const { key } = child;
205 | if (keysToLeave.current.indexOf(key) >= 0) {
206 | return getCoverAnimation(child, keysToLeave.current.indexOf(key), 'leave');
207 | }
208 | if (
209 | (keysToEnter.current.indexOf(key) >= 0 ||
210 | (isTween.current[key] && keysToLeave.current.indexOf(key) === -1)) &&
211 | !(isTween.current[key] === 'enter' && saveTweenTag.current[key])
212 | ) {
213 | /**
214 | * 1. 在 key 在 enter 里。
215 | * 2. 出场未结束,触发进场, this.isTween[key] 为 leave, key 在 enter 里。
216 | * 3. 状态为 enter 且 tweenTag 里有值时,不执行重载动画属性,直接调用 tweenTag 里的。
217 | */
218 | return getCoverAnimation(child, keysToEnter.current.indexOf(key), 'enter');
219 | }
220 | if (!oneEnter.current) {
221 | return getCoverAnimation(child, i, 'appear');
222 | }
223 | return saveTweenTag.current[key];
224 | });
225 | if (windowIsUndefined) {
226 | if (!component) {
227 | return <>{props.children}>;
228 | }
229 | return createElement(component, { ...tagProps, ...componentProps, ref }, props.children);
230 | }
231 | if (!component) {
232 | return childrenToRender[0] ? cloneElement(childrenToRender[0], { ref }) : null;
233 | }
234 | return createElement(component, { ...tagProps, ...componentProps, ref }, childrenToRender);
235 | });
236 |
237 | TweenOneGroup.displayName = 'TweenOneGroup';
238 | TweenOneGroup.isTweenOneGroup = true;
239 |
240 | export default TweenOneGroup;
241 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Ticker, Plugins, Easing } from 'tween-one';
2 | import TweenOne from './TweenOne';
3 | import TweenOneGroup from './TweenOneGroup';
4 | import ChildrenPlugin from './plugin/ChildrenPlugin';
5 | import PathMotionPlugin from './plugin/PathMotionPlugin';
6 | import SvgDrawPlugin from './plugin/SvgDrawPlugin';
7 | import SvgMorphPlugin from './plugin/SvgMorphPlugin';
8 |
9 | export * from './type';
10 |
11 | export {
12 | Easing,
13 | Ticker,
14 | Plugins,
15 | TweenOneGroup,
16 | ChildrenPlugin,
17 | PathMotionPlugin,
18 | SvgDrawPlugin,
19 | SvgMorphPlugin,
20 | };
21 |
22 | TweenOne.plugins = Plugins;
23 | TweenOne.ticker = Ticker;
24 | TweenOne.easing = Easing;
25 |
26 | export default TweenOne;
27 |
--------------------------------------------------------------------------------
/src/plugin/ChildrenPlugin.ts:
--------------------------------------------------------------------------------
1 | import type { IObject } from '../type';
2 |
3 | interface IFormatMoney {
4 | thousand?: string;
5 | decimal?: string;
6 | }
7 | interface IVars {
8 | value: number;
9 | floatLength: number;
10 | formatMoney?: IFormatMoney;
11 | }
12 |
13 | class ChildrenPlugin {
14 | static key: string = 'innerHTML';
15 |
16 | static className: string = 'Children';
17 |
18 | start?: IVars;
19 |
20 | startAt?: IObject;
21 |
22 | target?: HTMLElement;
23 |
24 | vars: IVars;
25 | key: string;
26 | // eslint-disable-next-line no-useless-constructor,no-empty-function
27 | constructor(vars: IVars, key: string) {
28 | this.vars = vars;
29 | this.key = key;
30 | }
31 |
32 | getAnimStart = () => {
33 | const { target, vars, startAt, key } = this;
34 | const { formatMoney } = vars;
35 | const opts = {
36 | thousand: (formatMoney && formatMoney.thousand) || ',',
37 | decimal: (formatMoney && formatMoney.decimal) || '.',
38 | };
39 | const rep = new RegExp(`\\${opts.thousand}`, 'g');
40 |
41 | this.start = startAt![key] || {
42 | value: parseFloat(target!.innerHTML.replace(rep, '').replace(opts.decimal, '.')) || 0,
43 | };
44 | return this.start;
45 | };
46 |
47 | toMoney = (v: string, _opts: IFormatMoney) => {
48 | const opts = {
49 | thousand: _opts.thousand || ',',
50 | decimal: _opts.decimal || '.',
51 | };
52 | const negative = parseFloat(v) < 0 ? '-' : '';
53 | const numberArray = v.split('.');
54 | const base = Math.abs(parseInt(numberArray[0], 10)).toString();
55 | const mod = base.length > 3 ? base.length % 3 : 0;
56 | const decimal = numberArray[1];
57 | return `${negative}${mod ? `${base.substr(0, mod)}${opts.thousand}` : ''}${base
58 | .substr(mod)
59 | .replace(/(\d{3})(?=\d)/g, `$1${opts.thousand}`)}${
60 | decimal ? `${opts.decimal}${decimal}` : ''
61 | }`;
62 | };
63 |
64 | render = (ratio: number) => {
65 | const { value, floatLength, formatMoney } = this.vars;
66 | let v: number | string = (value - this.start!.value) * ratio + this.start!.value;
67 | if (typeof floatLength === 'number') {
68 | if (floatLength) {
69 | v = v.toFixed(floatLength);
70 | const numberArray = v.toString().split('.');
71 | let decimal = numberArray[1] || '';
72 | decimal = decimal.length > floatLength ? decimal.substring(0, floatLength) : decimal;
73 | const l = floatLength - decimal.length;
74 | if (l) {
75 | Array(l)
76 | .fill(0)
77 | .forEach((num) => {
78 | decimal += `${num}`;
79 | });
80 | }
81 | v = `${numberArray[0]}.${decimal}`;
82 | } else {
83 | v = Math.round(v);
84 | }
85 | }
86 | v = formatMoney ? this.toMoney(`${v}`, formatMoney) : v;
87 | return v;
88 | };
89 | }
90 |
91 | export default ChildrenPlugin;
92 |
--------------------------------------------------------------------------------
/src/plugin/PathMotionPlugin.ts:
--------------------------------------------------------------------------------
1 | import PathMotionPlugin from 'tween-one/es/plugins/PathMotionPlugin';
2 |
3 | export default PathMotionPlugin;
4 |
--------------------------------------------------------------------------------
/src/plugin/SvgDrawPlugin.ts:
--------------------------------------------------------------------------------
1 | import SvgDrawPlugin from 'tween-one/es/plugins/SvgDrawPlugin';
2 |
3 | export default SvgDrawPlugin;
4 |
--------------------------------------------------------------------------------
/src/plugin/SvgMorphPlugin.ts:
--------------------------------------------------------------------------------
1 | import SvgMorph from 'tween-one/es/plugins/SvgMorphPlugin';
2 |
3 | export default SvgMorph;
4 |
--------------------------------------------------------------------------------
/src/type.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react';
2 | import type { IAnimObject as Anim, IMode, ITimelineCallBack as TimelineCallBack } from 'tween-one';
3 |
4 | export type IObject = Record;
5 |
6 | export type ITimelineCallBack = TimelineCallBack;
7 | export interface ICallBack {
8 | mode?: IMode;
9 | moment?: number;
10 | ratio?: number;
11 | index: number;
12 | repeat?: number;
13 | timelineMoment?: number;
14 | vars?: IObject | IObject[];
15 | targets?: IObject | IObject[];
16 | }
17 |
18 | interface AnimObject extends Anim {
19 | Children?: {
20 | value?: number;
21 | floatLength?: number;
22 | formatMoney?: true | { thousand?: string; decimal?: string };
23 | };
24 | }
25 |
26 | export type AnimObjectOrArray = AnimObject | AnimObject[];
27 |
28 | export type IAnimObject = AnimObjectOrArray | ((e: { key: string; index: number }) => AnimObject);
29 |
30 | interface AllHTMLAttributes
31 | extends Omit, 'crossOrigin'>,
32 | React.AllHTMLAttributes {}
33 | export interface IAnimProps extends Omit {
34 | style?: React.CSSProperties;
35 | children?: any;
36 | animation?: AnimObjectOrArray;
37 | paused?: boolean;
38 | delay?: number;
39 | reverse?: boolean;
40 | repeatDelay?: number;
41 | repeat?: number;
42 | yoyo?: boolean;
43 | ref?: React.Ref;
44 | onChange?: (v: ICallBack) => void;
45 | onChangeTimeline?: (v: ITimelineCallBack) => void;
46 | moment?: number;
47 | attr?: boolean;
48 | resetStyle?: boolean;
49 | component?: string | null | React.FC | React.ComponentClass;
50 | componentProps?: IObject;
51 | forcedJudg?: IObject;
52 | killPrevAnim?: boolean;
53 | regionStartTime?: number;
54 | regionEndTime?: number;
55 | }
56 |
57 | export interface IEndCallback {
58 | key?: string | React.ReactText;
59 | type?: string;
60 | target?: HTMLElement | HTMLElement[];
61 | }
62 |
63 | export interface IGroupProps extends Omit {
64 | ref?: React.Ref;
65 | appear?: boolean;
66 | enter?: IAnimObject;
67 | leave?: IAnimObject;
68 | animatingClassName?: string[];
69 | exclusive?: boolean;
70 | resetStyle?: boolean;
71 | onEnd?: (e: IEndCallback) => void;
72 | component?: string | null | React.FC | React.ComponentClass;
73 | componentProps?: IObject;
74 | }
75 |
76 | export interface TweenOneRef extends React.ForwardRefExoticComponent {
77 | isTweenOne?: boolean;
78 | plugins?: any;
79 | ticker?: any;
80 | easing?: any;
81 | }
82 |
83 | export interface TweenOneGroupRef extends React.ForwardRefExoticComponent {
84 | isTweenOneGroup?: boolean;
85 | }
86 |
--------------------------------------------------------------------------------
/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const windowIsUndefined = !(
4 | typeof window !== 'undefined' &&
5 | window.document &&
6 | window.document.createElement
7 | );
8 |
9 | export const useIsomorphicLayoutEffect = windowIsUndefined
10 | ? React.useEffect
11 | : React.useLayoutEffect;
12 |
--------------------------------------------------------------------------------
/src/utils/group.ts:
--------------------------------------------------------------------------------
1 | import type { ReactElement } from 'react';
2 | import React from 'react';
3 |
4 | import type { IObject } from '../type';
5 |
6 |
7 | export function toArrayChildren(children: any) {
8 | const ret: any[] = [];
9 | React.Children.forEach(children, (c) => {
10 | ret.push(c);
11 | });
12 | return ret;
13 | }
14 |
15 | export function findChildInChildrenByKey(children: ReactElement[], key: string | number | null) {
16 | let ret: any = null;
17 | if (children) {
18 | children.forEach((c) => {
19 | if (ret || !c) {
20 | return;
21 | }
22 | if (c.key === key) {
23 | ret = c;
24 | }
25 | });
26 | }
27 | return ret;
28 | }
29 |
30 | export function mergeChildren(prev: ReactElement[], next: ReactElement[]) {
31 | let ret: any[] = [];
32 | // For each key of `next`, the list of keys to insert before that key in
33 | // the combined list
34 | const nextChildrenPending: any = {};
35 | let pendingChildren: any[] = [];
36 | let followChildrenKey: string | number | null = null;
37 | prev.forEach((c) => {
38 | if (!c) {
39 | return;
40 | }
41 | if (c.key && findChildInChildrenByKey(next, c.key)) {
42 | if (pendingChildren.length) {
43 | nextChildrenPending[c.key] = pendingChildren;
44 | pendingChildren = [];
45 | }
46 | followChildrenKey = c.key;
47 | } else if (c.key) {
48 | pendingChildren.push(c);
49 | }
50 | });
51 | if (!followChildrenKey) {
52 | ret = ret.concat(pendingChildren);
53 | }
54 |
55 | next.forEach((c) => {
56 | if (!c) {
57 | return;
58 | }
59 | if (c.key && nextChildrenPending.hasOwnProperty(c.key)) {
60 | // eslint-disable-line no-prototype-builtins
61 | ret = ret.concat(nextChildrenPending[c.key]);
62 | }
63 | ret.push(c);
64 | if (c.key === followChildrenKey) {
65 | ret = ret.concat(pendingChildren);
66 | }
67 | });
68 |
69 | return ret;
70 | }
71 |
72 | export function transformArguments(arg: any, key: string | number | null, i: number) {
73 | let result;
74 | if (typeof arg === 'function') {
75 | result = arg({
76 | key,
77 | index: i,
78 | });
79 | } else {
80 | result = arg;
81 | }
82 | return result;
83 | }
84 |
85 | export function getChildrenFromProps(props: IObject) {
86 | return props && props.children;
87 | }
88 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export function dataToArray(vars: any) {
2 | if (!vars && vars !== 0) {
3 | return [];
4 | }
5 | if (Array.isArray(vars)) {
6 | return vars;
7 | }
8 | return [vars];
9 | }
10 |
11 | function deepEql(a: any, b: any) {
12 | if (!a || !b) {
13 | return false;
14 | }
15 | const $a = Object.keys(a);
16 | const $b = Object.keys(b);
17 | if ($a.length && $b.length && $a.length === $b.length) {
18 | return !$a.some((key) => {
19 | let aa = a[key];
20 | let bb = b[key];
21 | if (Array.isArray(aa) && Array.isArray(bb)) {
22 | const aaa = aa.join();
23 | const bbb = bb.join();
24 | if (aaa === bbb && !aaa.match(/\[object object\]/gi)) {
25 | aa = aaa;
26 | bb = bbb;
27 | }
28 | }
29 | return aa !== bb;
30 | });
31 | }
32 | return false;
33 | }
34 |
35 | export function objectEqual(obj1: any, obj2: any) {
36 | if (obj1 === obj2 || deepEql(obj1, obj2)) {
37 | return true;
38 | }
39 | if (!obj1 || !obj2 || Object.keys(obj1).length !== Object.keys(obj2).length) {
40 | return false;
41 | }
42 | // animation 写在标签上的进行判断是否相等, 判断每个参数有没有 function;
43 | let equalBool = true;
44 | const setEqualBool = ($a: any, $b: any) => {
45 | const objA = Object.keys($a).length > Object.keys($b).length ? $a : $b;
46 | const objB = Object.keys($a).length > Object.keys($b).length ? $b : $a;
47 | Object.keys(objA).forEach((key) => {
48 | // 如果前面有参数匹配不相同则直接返回;
49 | if (!equalBool) {
50 | return;
51 | }
52 | if (!(key in objB)) {
53 | equalBool = false;
54 | }
55 |
56 | if (typeof objA[key] === 'object' && typeof objB[key] === 'object') {
57 | equalBool = objectEqual(objA[key], objB[key]);
58 | } else if (typeof objA[key] === 'function' && typeof objB[key] === 'function') {
59 | if (objA[key].toString().replace(/\s+/g, '') !== objB[key].toString().replace(/\s+/g, '')) {
60 | equalBool = false;
61 | }
62 | } else if (objA[key] !== objB[key]) {
63 | equalBool = false;
64 | }
65 | });
66 | };
67 |
68 | if (Array.isArray(obj1) && Array.isArray(obj2)) {
69 | obj1.forEach((item, i) => {
70 | setEqualBool(item, obj2[i]);
71 | });
72 | } else {
73 | setEqualBool(obj1, obj2);
74 | }
75 | return equalBool;
76 | }
77 |
--------------------------------------------------------------------------------
/tests/tweenOne.test.jsx:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 | import React from 'react';
3 | import Enzyme, { mount } from 'enzyme';
4 | import Adapter from 'enzyme-adapter-react-16';
5 |
6 | import TweenOne, { Plugins, Ticker } from '../src';
7 |
8 | import ChildrenPlugin from '../src/plugin/ChildrenPlugin';
9 |
10 | import PathMotionPlugin from '../src/plugin/PathMotionPlugin';
11 |
12 | Plugins.push(ChildrenPlugin);
13 |
14 | Plugins.push(PathMotionPlugin);
15 |
16 | Enzyme.configure({ adapter: new Adapter() });
17 |
18 | const TweenComp = (props) => {
19 | return (
20 |
25 | );
26 | };
27 |
28 | describe('rc-tween-one', () => {
29 | let instance;
30 | it('single tween-one', (done) => {
31 | instance = mount(
32 | ,
52 | );
53 | const TweenNode = instance.instance().children[0];
54 | console.log('start:', TweenNode.style.top);
55 | expect(parseFloat(TweenNode.style.top)).toBe(0);
56 | Ticker.timeout(() => {
57 | // 默认时间为450,用500是肯定过值;
58 | console.log('end:', TweenNode.style.top);
59 | expect(parseFloat(TweenNode.style.top)).toBe(100);
60 | done();
61 | }, 600);
62 | });
63 | it('tween-one repeat -1 and yoyo', (done) => {
64 | instance = mount(
65 | ,
77 | );
78 | const TweenNode = instance.instance().children[0];
79 | console.log('start:', TweenNode.style.top);
80 | expect(parseFloat(TweenNode.style.top)).toBe(0);
81 | Ticker.timeout(() => {
82 | console.log('end:', TweenNode.style.top);
83 | expect(parseFloat(TweenNode.style.top)).toBeLessThan(2);
84 | done();
85 | }, 900);
86 | });
87 | it('single tween-one duration is 0', (done) => {
88 | let complete;
89 | instance = mount(
90 |
91 |
100 | 动画
101 |
102 |
,
103 | );
104 | Ticker.timeout(() => {
105 | console.log(complete);
106 | expect(complete).toBe(1);
107 | done();
108 | }, 34);
109 | });
110 | it('timeline tween-one', (done) => {
111 | instance = mount(
112 |
113 |
117 | 动画
118 |
119 |
,
120 | );
121 | const child = instance.instance().children[0];
122 |
123 | Ticker.timeout(() => {
124 | console.log('top===100,data:', child.style.top, '########', 'left>0,data:', child.style.left);
125 | expect(parseFloat(child.style.top)).toBe(100);
126 | expect(parseFloat(child.style.left)).toBeGreaterThan(0);
127 | Ticker.timeout(() => {
128 | console.log(
129 | 'top<100,data:',
130 | child.style.top,
131 | '########',
132 | 'left===0,data:',
133 | child.style.left,
134 | );
135 | expect(parseFloat(child.style.left)).toBe(100);
136 | expect(parseFloat(child.style.top)).toBeLessThan(100);
137 | Ticker.timeout(() => {
138 | console.log(
139 | 'top===0,data:',
140 | child.style.top,
141 | '########',
142 | 'left<100,data:',
143 | child.style.left,
144 | );
145 | expect(parseFloat(child.style.left)).toBeLessThan(100);
146 | expect(parseFloat(child.style.top)).toBe(0);
147 | Ticker.timeout(() => {
148 | console.log(
149 | 'top===0,data:',
150 | child.style.top,
151 | '########',
152 | 'left===0,data:',
153 | child.style.left,
154 | );
155 | expect(parseFloat(child.style.top)).toBe(0);
156 | expect(parseFloat(child.style.left)).toBe(0);
157 | done();
158 | }, 450);
159 | }, 450);
160 | }, 450);
161 | }, 500);
162 | });
163 | it('repeat tween-one', (done) => {
164 | instance = mount(
165 |
166 |
170 | 动画
171 |
172 |
,
173 | );
174 | const child = instance.instance().children[0];
175 | console.log('start:', child.style.top);
176 | expect(parseFloat(child.style.top)).toBe(0);
177 | Ticker.timeout(() => {
178 | expect(parseFloat(child.style.top)).toBeGreaterThan(95);
179 | console.log('20 milliseconds before the first repeat end, top>95, data:', child.style.top);
180 | Ticker.timeout(() => {
181 | expect(parseFloat(child.style.top)).toBeLessThan(10);
182 | console.log(
183 | '20 ms after the beginning of the second repeat, top<10, data',
184 | child.style.top,
185 | );
186 | Ticker.timeout(() => {
187 | expect(parseFloat(child.style.top)).toBe(100);
188 | console.log('repeat end,top:', child.style.top);
189 | done();
190 | }, 450);
191 | }, 340);
192 | }, 430);
193 | });
194 | it('attr is true tween-one', (done) => {
195 | instance = mount(
196 | ,
207 | );
208 | const child = instance.instance().children[0];
209 | Ticker.timeout(() => {
210 | const cx = child.getAttribute('cx');
211 | console.log('svg cx:', cx);
212 | expect(parseFloat(cx)).toBe(50);
213 | done();
214 | }, 500);
215 | });
216 | it('type is from tween-one', (done) => {
217 | instance = mount(
218 |
219 |
220 | 动画
221 |
222 |
,
223 | );
224 | const child = instance.instance().children[0];
225 | console.log(`default:${child.style.top}`);
226 | expect(parseFloat(child.style.top)).toBe(100);
227 | Ticker.timeout(() => {
228 | expect(parseFloat(child.style.top)).toBeGreaterThan(95);
229 | console.log(`start:${child.style.top}`);
230 | Ticker.timeout(() => {
231 | expect(parseFloat(child.style.top)).toBe(0);
232 | console.log(`end:${child.style.top}`);
233 | done();
234 | }, 450);
235 | }, 30);
236 | });
237 | it('is update Animation', (done) => {
238 | instance = mount(
239 | ,
243 | );
244 | let child = instance.find('.wrapper').instance().children[0];
245 | Ticker.timeout(() => {
246 | expect(parseFloat(child.style.top)).toBeGreaterThan(99);
247 | console.log(`child top:${child.style.top}`);
248 | instance.setProps({
249 | animation: { left: 100, y: 100 },
250 | });
251 | expect(instance.props().animation.left).toBe(100);
252 | child = instance.find('.wrapper').instance().children[0];
253 | Ticker.timeout(() => {
254 | expect(parseFloat(child.style.left)).toBeGreaterThan(99);
255 | console.log(`child left:${child.style.left}`);
256 | done();
257 | }, 540);
258 | }, 1040);
259 | });
260 | it('is update Animation2', (done) => {
261 | instance = mount();
262 | let child = instance.find('.wrapper').instance().children[0];
263 | Ticker.timeout(() => {
264 | expect(parseFloat(child.style.top)).toBeGreaterThan(99);
265 | console.log(`child top:${child.style.top}`);
266 | instance.setProps({
267 | animation: { left: 100 },
268 | });
269 | expect(instance.props().animation.left).toBe(100);
270 | child = instance.find('.wrapper').instance().children[0];
271 | Ticker.timeout(() => {
272 | expect(parseFloat(child.style.left)).toBeGreaterThan(99);
273 | console.log(`child left:${child.style.left}`);
274 | done();
275 | }, 540);
276 | }, 1040);
277 | });
278 | it('is update Animation no change', (done) => {
279 | instance = mount(
280 | {
286 | console.log('start');
287 | },
288 | },
289 | { y: 100, a: [100, 100] },
290 | ]}
291 | style={{ position: 'relative' }}
292 | />,
293 | );
294 | const child = instance.find('.wrapper').instance().children[0];
295 | Ticker.timeout(() => {
296 | console.log(`child top:${child.style.top}`);
297 | expect(parseFloat(child.style.top)).toBeGreaterThan(90);
298 |
299 | instance.setProps({
300 | animation: [
301 | {
302 | top: 100,
303 | color: 'red',
304 | onStart: () => {
305 | console.log('start');
306 | },
307 | },
308 | { y: 100, a: [100, 100] },
309 | ],
310 | });
311 |
312 | Ticker.timeout(() => {
313 | console.log(`child top:${child.style.top}`);
314 | expect(parseFloat(child.style.top)).toBe(100);
315 | done();
316 | }, 50);
317 | }, 440);
318 | });
319 | it('component is null tween-one', (done) => {
320 | instance = mount(
321 |
322 |
323 | 动画
324 |
325 |
,
326 | );
327 | const child = instance.find('.child').instance();
328 | console.log(`start: ${child.style.top}`);
329 | expect(parseFloat(child.style.top)).toBe(0);
330 | Ticker.timeout(() => {
331 | expect(parseFloat(child.style.top)).toBe(100);
332 | console.log(`end: ${child.style.top}`);
333 | done();
334 | }, 450);
335 | });
336 | it('component is null children is string warning', () => {
337 | instance = mount(
338 |
339 |
340 | 动画
341 |
342 |
,
343 | );
344 | const child = instance.find('div').instance().children;
345 | console.log(child.length, instance.text());
346 |
347 | expect(child.length).toBe(0);
348 | expect(instance.text()).toEqual('动画');
349 | });
350 | it('is resetStyle tween-one', (done) => {
351 | instance = mount(
352 | ,
353 | );
354 | let child = instance.find('.wrapper').instance().children[0];
355 | // eslint-disable-next-line no-underscore-dangle
356 | console.log('top', child._tweenOneVars, child.style, child.id);
357 | Ticker.timeout(() => {
358 | instance.setProps({
359 | animation: [{ left: 100 }, { opacity: 0 }],
360 | });
361 | Ticker.timeout(() => {
362 | child = instance.find('.wrapper').instance().children[0];
363 | // eslint-disable-next-line no-underscore-dangle
364 | console.log('top', child._tweenOneVars, child.style, child.id);
365 | // eslint-disable-next-line no-underscore-dangle
366 | expect(child._tweenOneVars.style.top).toEqual(undefined);
367 | done();
368 | }, 500);
369 | }, 100);
370 | });
371 | it('is reverse', (done) => {
372 | instance = mount(
373 | ,
379 | );
380 |
381 | Ticker.timeout(() => {
382 | instance.setProps({
383 | reverse: true,
384 | animation: {
385 | top: 100,
386 | },
387 | });
388 | Ticker.timeout(() => {
389 | const child = instance.find('.wrapper').instance().children[0];
390 | console.log(parseFloat(child.style.top));
391 | expect(parseFloat(child.style.top)).toBe(0);
392 | done();
393 | }, 350);
394 | }, 300);
395 | });
396 | it('is paused', (done) => {
397 | instance = mount(
398 | ,
404 | );
405 |
406 | Ticker.timeout(() => {
407 | let child = instance.find('.wrapper').instance().children[0];
408 | const top = parseFloat(child.style.top);
409 | expect(top).toBeGreaterThan(50);
410 | instance.setProps({
411 | paused: true,
412 | });
413 | Ticker.timeout(() => {
414 | child = instance.find('.wrapper').instance().children[0];
415 | console.log('top:', child.style.top);
416 | expect(parseFloat(child.style.top)).toBe(top);
417 | done();
418 | }, 100);
419 | }, 300);
420 | });
421 | it('is moment', (done) => {
422 | instance = mount(
423 | ,
431 | );
432 | const child = instance.find('.wrapper').instance().children[0];
433 | Ticker.timeout(() => {
434 | instance.setProps({
435 | moment: 1000,
436 | });
437 | Ticker.timeout(() => {
438 | console.log(child.style.top);
439 | expect(parseFloat(child.style.top)).toBe(100);
440 | done();
441 | }, 10);
442 | }, 100);
443 | });
444 |
445 | it('plugin: children plugin tween-one', (done) => {
446 | instance = mount(
447 |
448 | 0
449 |
,
450 | );
451 | console.log(`default:${instance.text()}`);
452 | expect(instance.text()).toEqual('0');
453 | Ticker.timeout(() => {
454 | expect(instance.text()).toEqual('1000');
455 | console.log(`end:${instance.text()}`);
456 | done();
457 | }, 450);
458 | });
459 | it('plugin: children plugin toMoney tween-one', (done) => {
460 | instance = mount(
461 |
462 |
463 | 0
464 |
465 |
,
466 | );
467 | console.log(`default:${instance.text()}`);
468 | expect(instance.text()).toEqual('0.00');
469 | Ticker.timeout(() => {
470 | expect(instance.text()).toEqual('1,000.00');
471 | console.log(`end:${instance.text()}`);
472 | done();
473 | }, 450);
474 | });
475 |
476 | it('plugin: pathMotion plugin tween-one', (done) => {
477 | instance = mount(
478 | ,
490 | );
491 | Ticker.timeout(() => {
492 | const child = instance.find('.wrapper').instance().children[0];
493 | console.log('transform:', child.style.transform);
494 | expect(child.style.transform).toEqual('translate(-15px,-15px) rotate(90deg)');
495 | done();
496 | }, 450);
497 | });
498 | });
499 |
--------------------------------------------------------------------------------
/tests/tweenOneGroup.test.jsx:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 | import React from 'react';
3 | import Enzyme, { mount } from 'enzyme';
4 | import Adapter from 'enzyme-adapter-react-16';
5 |
6 | import TweenOneGroup from '../src/TweenOneGroup';
7 |
8 | Enzyme.configure({ adapter: new Adapter() });
9 |
10 | const TweenOneGroupComp = (props) => {
11 | return (
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | describe('rc-tween-one-group', () => {
19 | let instance;
20 | function getFloat(str) {
21 | return parseFloat(str);
22 | }
23 |
24 | it('should render children', (done) => {
25 | instance = mount(
26 |
27 | a
28 | ,
29 | );
30 | const children = instance.find('p');
31 | console.log(children.length, instance.find('p'));
32 | expect(children.length).toBe(1);
33 | done();
34 | });
35 | it('appear is false', () => {
36 | instance = mount(
37 |
38 | a
39 | ,
40 | );
41 | const children = instance.find('p').instance();
42 | console.log('opacity:', children.style.opacity);
43 | expect(children.style.opacity).toBe('');
44 | });
45 | it('component is null', () => {
46 | instance = mount(
47 | ,
52 | );
53 | const children = instance.find('.wrapper').instance().children[0];
54 | console.log('tagName:', children.tagName);
55 | expect(children.tagName).toEqual('P');
56 | });
57 | it('is normal tween', (done) => {
58 | instance = mount(
59 |
60 | a
61 | ,
62 | );
63 |
64 | setTimeout(() => {
65 | const children = instance.find('p').instance();
66 | console.log('marginLeft < 100:', children.style.marginLeft);
67 | expect(getFloat(children.style.marginLeft)).toBeLessThan(100);
68 | setTimeout(() => {
69 | console.log('marginLeft is 0:', children.style.marginLeft);
70 | expect(getFloat(children.style.marginLeft)).toBe(0);
71 | done();
72 | }, 500);
73 | }, 50);
74 | });
75 | it('is switch children', (done) => {
76 | instance = mount();
77 | let children = instance.find('p').children();
78 | console.log('children length:', children.length);
79 | expect(children.length).toBe(0);
80 | setTimeout(() => {
81 | instance.setProps({
82 | children: [a
, b
],
83 | });
84 |
85 | setTimeout(() => {
86 | children = instance.find('.wrapper').instance().children[0].children;
87 | console.log('children length:', children.length);
88 | expect(children.length).toBe(2);
89 |
90 | instance.setProps({
91 | children: [a
],
92 | });
93 | setTimeout(() => {
94 | children = instance.find('.wrapper').instance().children[0].children;
95 | console.log('children length:', children.length);
96 | expect(children.length).toBe(1);
97 | done();
98 | }, 500);
99 | }, 500);
100 | }, 50);
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "typeRoots": ["./typings"],
9 | "declaration": true,
10 | "esModuleInterop": true,
11 | "sourceMap": true,
12 | "baseUrl": "./",
13 | "strict": true,
14 | "paths": {
15 | "@/*": ["src/*"],
16 | "@@/*": ["src/.umi/*"],
17 | "rc-tween-one": ["src/index.tsx"]
18 | },
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "exclude": [
22 | "node_modules",
23 | "lib",
24 | "es",
25 | "dist",
26 | "typings",
27 | "**/__test__",
28 | "test"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/typings/global/import.d.ts:
--------------------------------------------------------------------------------
1 | import * as CSS from 'csstype';
2 | declare module 'csstype' {
3 | interface Properties {
4 | [key: string]: any;
5 | }
6 | }
--------------------------------------------------------------------------------
/typings/global/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | declare module '*.css';
3 | declare module '*.less';
4 | declare module 'style-utils';
5 | declare module 'tween-functions';
6 | declare module 'raf';
7 | declare module 'flubber';
8 |
9 |
10 | // declare module 'rc-tween-one';
11 | declare module 'rc-tween-one/es/plugin/ChildrenPlugin';
12 | declare module 'rc-tween-one/es/plugin/PathMotionPlugin';
13 | declare module 'rc-tween-one/es/plugin/SvgDrawPlugin';
14 | declare module 'rc-tween-one/es/plugin/SvgMorphPlugin';
15 | declare module 'rc-tween-one/es/TweenOneGroup';
16 |
17 |
18 | interface Element {
19 | _tweenOneVars: any;
20 | }
21 |
--------------------------------------------------------------------------------