├── demos
├── demo1-chat-heads
│ ├── 0.jpg
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo4-photo-gallery
│ ├── 0.jpg
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo8-draggable-list
│ ├── cursor.png
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo7-water-ripples
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo0-simple-transition
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo2-draggable-balls
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
├── demo3-todomvc-list-transition
│ ├── index.jsx
│ ├── index.html
│ ├── Demo.jsx
│ └── index.css
├── demo5-spring-parameters-chooser
│ ├── index.jsx
│ ├── index.html
│ └── Demo.jsx
└── README.md
├── test
├── index.js
├── integration
│ ├── README.md
│ ├── package.json
│ ├── bower.json
│ └── bower.html
├── stripStyle-test.js
├── createMockRaf.js
├── mergeDiff-test.js
├── StaggeredMotion-test.js
├── Motion-test.js
└── TransitionMotion-test.js
├── .travis.yml
├── .eslintignore
├── .gitignore
├── .npmignore
├── src
├── presets.js
├── reorderKeys.js
├── spring.js
├── mapToZero.js
├── react-motion.js
├── stripStyle.js
├── shouldStopAnimation.js
├── stepper.js
├── Types.js
├── mergeDiff.js
├── Motion.js
├── StaggeredMotion.js
└── TransitionMotion.js
├── .babelrc
├── .size-snapshot.json
├── server.js
├── AUTHORS
├── .flowconfig
├── bower.json
├── LICENSE
├── webpack.config.js
├── karma.conf.js
├── rollup.config.js
├── .eslintrc
├── package.json
├── HISTORY.md
└── README.md
/demos/demo1-chat-heads/0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/0.jpg
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/1.jpg
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/2.jpg
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/3.jpg
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/4.jpg
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo1-chat-heads/5.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/0.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/1.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/2.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/3.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/4.jpg
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo4-photo-gallery/5.jpg
--------------------------------------------------------------------------------
/demos/demo8-draggable-list/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenglou/react-motion/HEAD/demos/demo8-draggable-list/cursor.png
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const testsContext = require.context('./', true, /-test\.js$/);
2 |
3 | testsContext.keys().forEach(testsContext);
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - 10
5 |
6 | script:
7 | - npm run -s lint
8 | - npm run -s flow_check
9 | - npm run -s test:travis
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | coverage
3 | lib
4 | node_modules
5 | **/*/all.js
6 | webpack.*.js
7 | server.js
8 | karma.*.js
9 | test/integration
10 | demos/demo6/babel.js
11 | demos/*/Demo.jsx
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 |
4 | pids
5 | *.pid
6 | *.seed
7 |
8 | coverage
9 | node_modules
10 | bower_components
11 |
12 | .DS_Store
13 |
14 | /demos/**/all.*
15 | /build/
16 | /lib/
17 |
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo7-water-ripples/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo8-draggable-list/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo0-simple-transition/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo2-draggable-balls/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo3-todomvc-list-transition/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/demos/demo5-spring-parameters-chooser/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './Demo';
4 |
5 | ReactDOM.render(, document.querySelector('#content'));
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.*
2 |
3 | /bower_components/
4 |
5 | /coverage/
6 | /demos/
7 | /test/
8 |
9 | /karma.conf.js
10 | /webpack.*
11 | /server.js
12 |
13 | # sublime
14 | /*.sublime-project
15 | /*.sublime-workspace
16 |
17 | /rollup.config.js
18 |
--------------------------------------------------------------------------------
/demos/README.md:
--------------------------------------------------------------------------------
1 | ## Demos folder
2 |
3 | **Note**: since this is the master branch, the demos might be a bit ahead of the current stable API. [This commit](https://github.com/chenglou/react-motion/tree/0627243316c564f6c2f480bf615b82135f649a0a/demos) contains the stable demos.
4 |
--------------------------------------------------------------------------------
/src/presets.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | export default {
3 | noWobble: { stiffness: 170, damping: 26 }, // the default, if nothing provided
4 | gentle: { stiffness: 120, damping: 14 },
5 | wobbly: { stiffness: 180, damping: 12 },
6 | stiff: { stiffness: 210, damping: 20 },
7 | };
8 |
--------------------------------------------------------------------------------
/demos/demo3-todomvc-list-transition/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | RedoMVC
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/integration/README.md:
--------------------------------------------------------------------------------
1 | Simple folder for testing whether the release worked or not.
2 |
3 | Please run this:
4 | ```
5 | rm -rf bower_components
6 | rm -rf node_modules
7 | bower install
8 | npm install
9 | node -e 'console.log(require("react-motion"))'
10 | ```
11 |
12 | Check that the output of that looks normal.
13 |
14 | For Bower, please also open up bower.html
15 |
--------------------------------------------------------------------------------
/src/reorderKeys.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | let hasWarned = false;
4 | export default function reorderKeys() {
5 | if (process.env.NODE_ENV === 'development') {
6 | if (!hasWarned) {
7 | hasWarned = true;
8 | console.error(
9 | "`reorderKeys` has been removed, since it is no longer needed for TransitionMotion's new styles array API.",
10 | );
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/spring.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import presets from './presets';
3 | import type { OpaqueConfig, SpringHelperConfig } from './Types';
4 |
5 | const defaultConfig = {
6 | ...presets.noWobble,
7 | precision: 0.01,
8 | };
9 |
10 | export default function spring(
11 | val: number,
12 | config?: SpringHelperConfig,
13 | ): OpaqueConfig {
14 | return { ...defaultConfig, ...config, val };
15 | }
16 |
--------------------------------------------------------------------------------
/src/mapToZero.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import type { PlainStyle, Style } from './Types';
3 |
4 | // currently used to initiate the velocity style object to 0
5 | export default function mapToZero(obj: Style | PlainStyle): PlainStyle {
6 | let ret = {};
7 | for (const key in obj) {
8 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
9 | ret[key] = 0;
10 | }
11 | }
12 | return ret;
13 | }
14 |
--------------------------------------------------------------------------------
/test/integration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integration",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "Simple folder for testing whether the release worked or not.",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react-motion": "^0.5.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/stripStyle-test.js:
--------------------------------------------------------------------------------
1 | import stripStyle from '../src/stripStyle';
2 | import spring from '../src/spring';
3 |
4 | describe('stripStyle', () => {
5 | it('should return spring object into value', () => {
6 | expect(stripStyle({a: spring(1, [1, 2])})).toEqual({a: 1});
7 | });
8 |
9 | it('should ignore non-configured values', () => {
10 | expect(stripStyle({a: 10, b: 0})).toEqual({a: 10, b: 0});
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env", { "modules": "commonjs", "loose": true }],
4 | "@babel/flow",
5 | "@babel/react"
6 | ],
7 | "plugins": [
8 | ["@babel/proposal-class-properties", { "loose": true }],
9 | ["transform-react-remove-prop-types", { "mode": "unsafe-wrap" }]
10 | ],
11 | "env": {
12 | "test": {
13 | "plugins": [
14 | "@babel/transform-modules-commonjs"
15 | ]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/integration/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integration",
3 | "version": "0.4.1",
4 | "homepage": "https://github.com/chenglou/react-motion",
5 | "authors": [
6 | "Cheng Lou "
7 | ],
8 | "license": "MIT",
9 | "ignore": [
10 | "**/.*",
11 | "node_modules",
12 | "bower_components",
13 | "test",
14 | "tests"
15 | ],
16 | "dependencies": {
17 | "react-motion": "https://unpkg.com/react-motion/bower.zip"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/react-motion.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | export { default as Motion } from './Motion';
3 | export { default as StaggeredMotion } from './StaggeredMotion';
4 | export { default as TransitionMotion } from './TransitionMotion';
5 | export { default as spring } from './spring';
6 | export { default as presets } from './presets';
7 | export { default as stripStyle } from './stripStyle';
8 |
9 | // deprecated, dummy warning function
10 | export { default as reorderKeys } from './reorderKeys';
11 |
--------------------------------------------------------------------------------
/src/stripStyle.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | // turn {x: {val: 1, stiffness: 1, damping: 2}, y: 2} generated by
3 | // `{x: spring(1, {stiffness: 1, damping: 2}), y: 2}` into {x: 1, y: 2}
4 |
5 | import type { Style, PlainStyle } from './Types';
6 |
7 | export default function stripStyle(style: Style): PlainStyle {
8 | let ret = {};
9 | for (const key in style) {
10 | if (!Object.prototype.hasOwnProperty.call(style, key)) {
11 | continue;
12 | }
13 | ret[key] = typeof style[key] === 'number' ? style[key] : style[key].val;
14 | }
15 | return ret;
16 | }
17 |
--------------------------------------------------------------------------------
/test/integration/bower.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.size-snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "build/react-motion.js": {
3 | "bundled": 77059,
4 | "minified": 22596,
5 | "gzipped": 6616
6 | },
7 | "build/react-motion.min.js": {
8 | "bundled": 52481,
9 | "minified": 15210,
10 | "gzipped": 4446
11 | },
12 | "lib/react-motion.esm.js": {
13 | "bundled": 43290,
14 | "minified": 14594,
15 | "gzipped": 3583,
16 | "treeshaked": {
17 | "rollup": {
18 | "code": 6381,
19 | "import_statements": 196
20 | },
21 | "webpack": {
22 | "code": 7588
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | var webpack = require('webpack');
6 | var WebpackDevServer = require('webpack-dev-server');
7 | var config = require('./webpack.config');
8 | var port = process.env.PORT || 3000;
9 |
10 | new WebpackDevServer(webpack(config), {
11 | publicPath: config.output.publicPath,
12 | hot: true,
13 | stats: {
14 | chunkModules: false,
15 | colors: true,
16 | }
17 | }).listen(port, '0.0.0.0', function (err) {
18 | if (err) {
19 | console.log(err);
20 | }
21 |
22 | console.log('Listening at 0.0.0.0:' + port);
23 | });
24 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Adrian le Bas
2 | Amadeus Junqueira
3 | Benjamin San Souci
4 | Bishop Zareh
5 | Brenton Simpson
6 | Cesar Andreu
7 | Cheng Lou
8 | Dan Abramov
9 | Daniel Dunderfelt
10 | Dustan Kasten
11 | Frederick Fogerty
12 | Gaëtan Renaudeau
13 | Google, Inc.
14 | Henry Zhu
15 | Ivan Starkov
16 | Jeroen van Aert
17 | Jesper Petersson
18 | Jevgeni Geimanen
19 | Joe Lencioni
20 | John Amiah Ford
21 | Jon Lebensold
22 | Justin Morris
23 | Kyle Mathews
24 | Ludovico Fischer
25 | Michael J Hoffman
26 | Mirko Mariani
27 | Neil Kistner
28 | Nik Butenko
29 | Nikhil Baradwaj
30 | Olivier Tassinari
31 | Paolo Moretti
32 | Raymond Zhou
33 | Robert Haritonov
34 | Sorin Iclanzan
35 | Stefan Dombrowski
36 | Stephen J. Collings
37 | Sundeep Malladi
38 | Sunil Pai
39 | Travis Arnold
40 | Wilfred Denton
41 |
--------------------------------------------------------------------------------
/demos/demo0-simple-transition/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Toggle
7 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | How Many Demos Do You Need
7 |
8 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/fbjs/lib/PromiseMap.js
3 | .*/node_modules/fbjs/lib/fetchWithRetries.js
4 | .*/node_modules/fbjs/lib/Deferred.js.flow
5 | .*/node_modules/fbjs/lib/equalsSet.js.flow
6 | .*/node_modules/fbjs/lib/shallowEqual.js.flow
7 | .*/node_modules/fbjs/lib/someSet.js.flow
8 | .*/node_modules/fbjs/lib/everySet.js.flow
9 | .*/node_modules/fbjs/lib/UnicodeBidi.js.flow
10 | .*/node_modules/fbjs/lib/UnicodeBidiService.js.flow
11 | .*/node_modules/kefir/kefir.js.flow
12 | .*/node_modules/kefir/dist/kefir.js.flow
13 | .*/test/.*
14 | .*/node_modules/@webassemblyjs/.*
15 | .*/node_modules/eslint-plugin-jsx-a11y/.*
16 | .*/node_modules/rollup-plugin-size-snapshot/.*
17 | .*/node_modules/babel-plugin-transform-react-remove-prop-types
18 |
19 | [include]
20 |
21 | [libs]
22 |
23 | [options]
24 |
--------------------------------------------------------------------------------
/demos/demo7-water-ripples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ripples
7 |
8 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-motion",
3 | "version": "0.5.1",
4 | "homepage": "https://github.com/chenglou/react-motion",
5 | "authors": [
6 | "chenglou"
7 | ],
8 | "description": "A spring that solves your animation problems.",
9 | "main": [
10 | "build/react-motion.js",
11 | "build/react-motion.map"
12 | ],
13 | "dependencies": {
14 | "react": "^0.14.9 || ^15.3.0"
15 | },
16 | "keywords": [
17 | "react",
18 | "component",
19 | "react-component",
20 | "transitiongroup",
21 | "spring",
22 | "tween",
23 | "motion",
24 | "animation",
25 | "transition",
26 | "ui"
27 | ],
28 | "license": "MIT",
29 | "ignore": [
30 | "**/.*",
31 | "node_modules",
32 | "test",
33 | "demo*",
34 | "server.js",
35 | "src",
36 | "webpack.config.js",
37 | "webpack.prod.config.js",
38 | "karma.conf.js",
39 | "package.json"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/test/createMockRaf.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | type Callback = (now: number) => void;
4 |
5 | export default function (): Object {
6 | let allCallbacks = [];
7 | let prevTime = 0;
8 | let id = 0;
9 |
10 | const now = () => prevTime;
11 |
12 | const raf = (cb: Callback) => {
13 | id++;
14 | allCallbacks.push({id, cb});
15 | return id;
16 | };
17 |
18 | raf.cancel = id2 => {
19 | allCallbacks = allCallbacks.filter(item => item.id !== id2);
20 | };
21 |
22 | const defaultTimeInterval = 1000 / 60;
23 | const singleStep = ms => {
24 | const allCallbacksBefore = allCallbacks;
25 | allCallbacks = [];
26 |
27 | prevTime += ms;
28 | allCallbacksBefore.forEach(({cb}) => cb(prevTime));
29 | };
30 |
31 | const step = (howMany = 1, ms = defaultTimeInterval) => {
32 | for (let i = 0; i < howMany; i++) {
33 | singleStep(ms);
34 | }
35 | };
36 |
37 | return {now, raf, step};
38 | }
39 |
--------------------------------------------------------------------------------
/src/shouldStopAnimation.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import type { PlainStyle, Style, Velocity } from './Types';
3 |
4 | // usage assumption: currentStyle values have already been rendered but it says
5 | // nothing of whether currentStyle is stale (see unreadPropStyle)
6 | export default function shouldStopAnimation(
7 | currentStyle: PlainStyle,
8 | style: Style,
9 | currentVelocity: Velocity,
10 | ): boolean {
11 | for (let key in style) {
12 | if (!Object.prototype.hasOwnProperty.call(style, key)) {
13 | continue;
14 | }
15 |
16 | if (currentVelocity[key] !== 0) {
17 | return false;
18 | }
19 |
20 | const styleValue =
21 | typeof style[key] === 'number' ? style[key] : style[key].val;
22 | // stepper will have already taken care of rounding precision errors, so
23 | // won't have such thing as 0.9999 !=== 1
24 | if (currentStyle[key] !== styleValue) {
25 | return false;
26 | }
27 | }
28 |
29 | return true;
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 React Motion authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/demos/demo2-draggable-balls/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Grid of Balls
7 |
8 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Chat Heads
7 |
8 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/demos/demo0-simple-transition/Demo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Motion, spring} from '../../src/react-motion';
3 |
4 | export default class Demo extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {open: false};
8 | };
9 |
10 | handleMouseDown = () => {
11 | this.setState({open: !this.state.open});
12 | };
13 |
14 | handleTouchStart = (e) => {
15 | e.preventDefault();
16 | this.handleMouseDown();
17 | };
18 |
19 | render() {
20 | return (
21 |
22 |
27 |
28 |
29 | {({x}) =>
30 | // children is a callback which should accept the current value of
31 | // `style`
32 |
38 | }
39 |
40 |
41 | );
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/src/stepper.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | // stepper is used a lot. Saves allocation to return the same array wrapper.
4 | // This is fine and danger-free against mutations because the callsite
5 | // immediately destructures it and gets the numbers inside without passing the
6 | // array reference around.
7 | let reusedTuple: [number, number] = [0, 0];
8 | export default function stepper(
9 | secondPerFrame: number,
10 | x: number,
11 | v: number,
12 | destX: number,
13 | k: number,
14 | b: number,
15 | precision: number,
16 | ): [number, number] {
17 | // Spring stiffness, in kg / s^2
18 |
19 | // for animations, destX is really spring length (spring at rest). initial
20 | // position is considered as the stretched/compressed position of a spring
21 | const Fspring = -k * (x - destX);
22 |
23 | // Damping, in kg / s
24 | const Fdamper = -b * v;
25 |
26 | // usually we put mass here, but for animation purposes, specifying mass is a
27 | // bit redundant. you could simply adjust k and b accordingly
28 | // let a = (Fspring + Fdamper) / mass;
29 | const a = Fspring + Fdamper;
30 |
31 | const newV = v + a * secondPerFrame;
32 | const newX = x + newV * secondPerFrame;
33 |
34 | if (Math.abs(newV) < precision && Math.abs(newX - destX) < precision) {
35 | reusedTuple[0] = destX;
36 | reusedTuple[1] = 0;
37 | return reusedTuple;
38 | }
39 |
40 | reusedTuple[0] = newX;
41 | reusedTuple[1] = newV;
42 | return reusedTuple;
43 | }
44 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | mode: "development",
5 | devtool:
6 | process.env.NODE_ENV === 'development' ? 'eval-source-map' : 'source-map',
7 | entry: {
8 | 'demo0-simple-transition': './demos/demo0-simple-transition/index.jsx',
9 | 'demo1-chat-heads': './demos/demo1-chat-heads/index.jsx',
10 | 'demo2-draggable-balls': './demos/demo2-draggable-balls/index.jsx',
11 | 'demo3-todomvc-list-transition': './demos/demo3-todomvc-list-transition/index.jsx',
12 | 'demo4-photo-gallery': './demos/demo4-photo-gallery/index.jsx',
13 | 'demo5-spring-parameters-chooser': './demos/demo5-spring-parameters-chooser/index.jsx',
14 | 'demo7-water-ripples': './demos/demo7-water-ripples/index.jsx',
15 | 'demo8-draggable-list': './demos/demo8-draggable-list/index.jsx',
16 | },
17 | output: {
18 | filename: '[name]/all.js',
19 | publicPath: '/demos/',
20 | path: __dirname + '/demos/',
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.jsx?$/,
26 | exclude: /build|lib|bower_components|node_modules/,
27 | loader: 'babel-loader'
28 | },
29 | {
30 | test: /\.css$/,
31 | use: ['style-loader', 'css-loader']
32 | },
33 | {
34 | test: /\.jsx?$/,
35 | loader: 'eslint-loader',
36 | exclude: /build|lib|bower_components|node_modules/
37 | },
38 | ],
39 | },
40 | resolve: {
41 | extensions: ['.js', '.jsx']
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/demos/demo1-chat-heads/Demo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {StaggeredMotion, spring, presets} from '../../src/react-motion';
3 | import range from 'lodash.range';
4 |
5 | export default class Demo extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {x: 250, y: 300};
9 | };
10 |
11 | componentDidMount() {
12 | window.addEventListener('mousemove', this.handleMouseMove);
13 | window.addEventListener('touchmove', this.handleTouchMove);
14 | };
15 |
16 | handleMouseMove = ({pageX: x, pageY: y}) => {
17 | this.setState({x, y});
18 | };
19 |
20 | handleTouchMove = ({touches}) => {
21 | this.handleMouseMove(touches[0]);
22 | };
23 |
24 | getStyles = (prevStyles) => {
25 | // `prevStyles` is the interpolated value of the last tick
26 | const endValue = prevStyles.map((_, i) => {
27 | return i === 0
28 | ? this.state
29 | : {
30 | x: spring(prevStyles[i - 1].x, presets.gentle),
31 | y: spring(prevStyles[i - 1].y, presets.gentle),
32 | };
33 | });
34 | return endValue;
35 | };
36 |
37 | render() {
38 | return (
39 | ({x: 0, y: 0}))}
41 | styles={this.getStyles}>
42 | {balls =>
43 |
44 | {balls.map(({x, y}, i) =>
45 |
53 | )}
54 |
55 | }
56 |
57 | );
58 | };
59 | }
60 |
--------------------------------------------------------------------------------
/demos/demo8-draggable-list/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Framer cards
7 |
8 |
69 |
70 |
71 |
72 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/demos/demo7-water-ripples/Demo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TransitionMotion, spring} from '../../src/react-motion';
3 |
4 | const leavingSpringConfig = {stiffness: 60, damping: 15};
5 |
6 | export default class Demo extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {mouse: [], now: 't' + 0};
10 | };
11 |
12 | handleMouseMove = ({pageX, pageY}) => {
13 | // Make sure the state is queued and not batched.
14 | this.setState(() => {
15 | return {
16 | mouse: [pageX - 25, pageY - 25],
17 | now: 't' + Date.now(),
18 | };
19 | });
20 | };
21 |
22 | handleTouchMove = (e) => {
23 | e.preventDefault();
24 | this.handleMouseMove(e.touches[0]);
25 | };
26 |
27 | willLeave = (styleCell) => {
28 | return {
29 | ...styleCell.style,
30 | opacity: spring(0, leavingSpringConfig),
31 | scale: spring(2, leavingSpringConfig),
32 | };
33 | };
34 |
35 | render() {
36 | const {mouse: [mouseX, mouseY], now} = this.state;
37 | const styles = mouseX == null ? [] : [{
38 | key: now,
39 | style: {
40 | opacity: spring(1),
41 | scale: spring(0),
42 | x: spring(mouseX),
43 | y: spring(mouseY),
44 | }
45 | }];
46 | return (
47 |
48 | {circles =>
49 |
53 | {circles.map(({key, style: {opacity, scale, x, y}}) =>
54 |
63 | )}
64 |
65 | }
66 |
67 | );
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 |
5 | var withCoverage = process.argv.indexOf('coverage') !== -1 || process.env.COVERAGE;
6 |
7 | var webpackConfig = {
8 | mode: 'development',
9 | module: {
10 | rules: withCoverage ?
11 | [
12 | {
13 | test: /\.js$/,
14 | loader: 'babel-loader',
15 | include: [path.resolve('./test')]
16 | },
17 | {
18 | test: /\.js$/,
19 | loader: 'isparta-loader',
20 | include: [path.resolve('./src')]
21 | },
22 | ] :
23 | [
24 | {
25 | test: /\.js$/,
26 | loader: 'babel-loader',
27 | include: [path.resolve('./src'), path.resolve('./test')],
28 | },
29 | ],
30 | },
31 | stats: {
32 | colors: true,
33 | }
34 | };
35 |
36 | module.exports = function (config) {
37 | config.set({
38 | basePath: '',
39 | frameworks: ['jasmine'],
40 | files: [
41 | './node_modules/@babel/polyfill/browser.js',
42 | 'test/index.js',
43 | ],
44 | webpack: webpackConfig,
45 | webpackMiddleware: {
46 | stats: {
47 | chunkModules: false,
48 | colors: true,
49 | },
50 | },
51 | exclude: [],
52 | preprocessors: {
53 | 'test/index.js': ['webpack'],
54 | },
55 | reporters: ['jasmine-diff', 'progress'],
56 | jasmineDiffReporter: {
57 | pretty: true,
58 | color: {
59 | expectedBg: '',
60 | expectedFg: 'red',
61 | actualBg: '',
62 | actualFg: 'green',
63 | defaultBg: '',
64 | defaultFg: 'grey'
65 | }
66 | },
67 | coverageReporter: {
68 | dir: './coverage/',
69 | subdir: '.',
70 | reporters: [
71 | {type: 'html'},
72 | {type: 'lcovonly'},
73 | {type: 'text', file: 'text.txt'},
74 | {type: 'text-summary', file: 'text-summary.txt'},
75 | ],
76 | },
77 | captureTimeout: 90000,
78 | browserNoActivityTimeout: 60000,
79 | port: 9876,
80 | colors: true,
81 | logLevel: config.LOG_INFO,
82 | autoWatch: false,
83 | browsers: ['ChromeHeadless'],
84 | singleRun: true,
85 | });
86 | };
87 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import nodeResolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import babel from 'rollup-plugin-babel';
4 | import replace from 'rollup-plugin-replace';
5 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot';
6 | import { uglify } from 'rollup-plugin-uglify';
7 | import pkg from './package.json';
8 |
9 | const input = './src/react-motion.js';
10 | const name = 'ReactMotion';
11 | const globals = {
12 | react: 'React',
13 | };
14 |
15 | // treat as external "module/path" modules and reserved rollup paths
16 | const external = id =>
17 | !id.startsWith('\0') && !id.startsWith('.') && !id.startsWith('/');
18 |
19 | const getBabelOptions = () => ({
20 | babelrc: false,
21 | exclude: '**/node_modules/**',
22 | runtimeHelpers: true,
23 | plugins: [
24 | ['@babel/proposal-class-properties', { loose: true }],
25 | ['transform-react-remove-prop-types', { mode: 'unsafe-wrap' }],
26 | ['@babel/transform-runtime', { useESModules: true }],
27 | ],
28 | presets: [
29 | ['@babel/env', { modules: false, loose: true }],
30 | '@babel/flow',
31 | '@babel/react',
32 | ],
33 | });
34 |
35 | const commonjsOptions = {
36 | include: '**/node_modules/**',
37 | };
38 |
39 | export default [
40 | {
41 | input,
42 | output: { file: 'build/react-motion.js', format: 'umd', name, globals },
43 | external: Object.keys(globals),
44 | plugins: [
45 | nodeResolve(),
46 | babel(getBabelOptions()),
47 | commonjs(commonjsOptions),
48 | replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
49 | sizeSnapshot(),
50 | ],
51 | },
52 |
53 | {
54 | input,
55 | output: { file: 'build/react-motion.min.js', format: 'umd', name, globals },
56 | external: Object.keys(globals),
57 | plugins: [
58 | nodeResolve(),
59 | babel(getBabelOptions()),
60 | commonjs(commonjsOptions),
61 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
62 | sizeSnapshot(),
63 | uglify(),
64 | ],
65 | },
66 |
67 | {
68 | input,
69 | output: { file: pkg.module, format: 'esm' },
70 | external,
71 | plugins: [babel(getBabelOptions()), sizeSnapshot()],
72 | },
73 | ];
74 |
--------------------------------------------------------------------------------
/demos/demo4-photo-gallery/Demo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Motion, spring} from '../../src/react-motion';
3 |
4 | const springSettings = {stiffness: 170, damping: 26};
5 | const NEXT = 'show-next';
6 |
7 | export default class Demo extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | photos: [[500, 350], [800, 600], [800, 400], [700, 500], [200, 650], [600, 600]],
12 | currPhoto: 0,
13 | };
14 | };
15 |
16 | handleChange = ({target: {value}}) => {
17 | this.setState({currPhoto: value});
18 | };
19 |
20 | clickHandler = (btn) => {
21 | let photoIndex = btn === NEXT ? this.state.currPhoto+1 : this.state.currPhoto-1;
22 |
23 | photoIndex = photoIndex >= 0 ? photoIndex : this.state.photos.length - 1;
24 | photoIndex = photoIndex >= this.state.photos.length ? 0 : photoIndex;
25 |
26 | this.setState({
27 | currPhoto: photoIndex
28 | })
29 | };
30 |
31 | render() {
32 | const {photos, currPhoto} = this.state;
33 | const [currWidth, currHeight] = photos[currPhoto];
34 |
35 | const widths = photos.map(([origW, origH]) => currHeight / origH * origW);
36 |
37 | const leftStartCoords = widths
38 | .slice(0, currPhoto)
39 | .reduce((sum, width) => sum - width, 0);
40 |
41 | let configs = [];
42 | photos.reduce((prevLeft, [origW, origH], i) => {
43 | configs.push({
44 | left: spring(prevLeft, springSettings),
45 | height: spring(currHeight, springSettings),
46 | width: spring(widths[i], springSettings),
47 | });
48 | return prevLeft + widths[i];
49 | }, leftStartCoords);
50 |
51 | return (
52 |
53 |
Scroll Me
54 |
55 |
61 |
62 |
63 |
64 | {container =>
65 |
66 | {configs.map((style, i) =>
67 |
68 | {style =>
69 |
70 | }
71 |
72 | )}
73 |
74 | }
75 |
76 |
77 |
78 | );
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb", "prettier"],
4 | "env": {
5 | "browser": true,
6 | "es6": true,
7 | "jasmine": true
8 | },
9 | "rules": {
10 | "eqeqeq": [2, "allow-null"],
11 | "id-length": 0,
12 | "no-console": 0, // use it for warnings
13 | "no-nested-ternary": 0,
14 | "prefer-const": 0,
15 | "no-multiple-empty-lines": 0,
16 | "no-useless-escape": 0,
17 | "no-else-return": 0,
18 | "comma-dangle": 0,
19 | "lines-between-class-members": 0,
20 | "indent": 0,
21 | "operator-linebreak": 0,
22 | "object-curly-newline": 0,
23 | "function-paren-newline": 0,
24 | "prefer-destructuring": 0,
25 | "camelcase": ["error", {"allow": ["^UNSAFE_"]}],
26 |
27 | "react/jsx-wrap-multilines": 0,
28 | "react/jsx-one-expression-per-line": 0,
29 | "react/no-unused-state": 0,
30 | "react/require-default-props": 0,
31 | "react/destructuring-assignment": 0,
32 | "react/no-did-mount-set-state": 2,
33 | "react/no-multi-comp": 0,
34 | "react/jsx-boolean-value": [2, "always"],
35 | "react/sort-comp": [
36 | 2, {
37 | "order": [
38 | "displayName",
39 | "propTypes",
40 | "contextTypes",
41 | "childContextTypes",
42 | "mixins",
43 | "statics",
44 | "defaultProps",
45 | "getDefaultProps",
46 | "getInitialState",
47 | "getChildContext",
48 | "componentWillMount",
49 | "UNSAFE_componentWillMount",
50 | "componentDidMount",
51 | "componentWillReceiveProps",
52 | "UNSAFE_componentWillReceiveProps",
53 | "shouldComponentUpdate",
54 | "componentWillUpdate",
55 | "UNSAFE_componentWillUpdate",
56 | "componentDidUpdate",
57 | "componentWillUnmount",
58 | "/^on.+$/",
59 | "/^get.+$/",
60 | "/^render.+$/",
61 | "/^.+$/", // All other methods go here
62 | "render"
63 | ]
64 | }
65 | ],
66 |
67 | "max-len": 0,
68 | "no-mixed-operators": 0,
69 | "no-continue": 0,
70 | "no-restricted-syntax": 0,
71 | "no-plusplus": 0,
72 | "no-confusing-arrow": 0,
73 | "arrow-parens": 0,
74 | "arrow-body-style": 0,
75 | "react/jsx-indent": 0,
76 | "react/jsx-indent-props": 0,
77 | "react/jsx-closing-bracket-location": 0,
78 | "react/prefer-es6-class": 0,
79 | "react/jsx-filename-extension": 0,
80 | "react/prefer-stateless-function": 0,
81 | "object-curly-spacing": 0,
82 | "import/imports-first": 0,
83 | "import/no-unresolved": 0,
84 | "import/no-extraneous-dependencies": 0,
85 | "import/order": 0,
86 | "import/no-webpack-loader-syntax": 0,
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Types.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | // Babel 5.x doesn't support type parameters, so we make this alias here out of
4 | // Babel's sight.
5 | /* eslint-disable spaced-comment, no-undef */
6 | /*::
7 | import type {Element} from 'react';
8 | export type ReactElement = Element<*>;
9 | */
10 |
11 | // === basic reused types ===
12 | // type of the second parameter of `spring(val, config)` all fields are optional
13 | export type SpringHelperConfig = {
14 | stiffness?: number,
15 | damping?: number,
16 | precision?: number,
17 | };
18 | // the object returned by `spring(value, yourConfig)`. For internal usage only!
19 | export type OpaqueConfig = {
20 | val: number,
21 | stiffness: number,
22 | damping: number,
23 | precision: number,
24 | };
25 | // your typical style object given in props. Maps to a number or a spring config
26 | export type Style = { [key: string]: number | OpaqueConfig };
27 | // the interpolating style object, with the same keys as the above Style object,
28 | // with the values mapped to numbers, naturally
29 | export type PlainStyle = { [key: string]: number };
30 | // internal velocity object. Similar to PlainStyle, but whose numbers represent
31 | // speed. Might be exposed one day.
32 | export type Velocity = { [key: string]: number };
33 |
34 | // === Motion ===
35 | export type MotionProps = {
36 | defaultStyle?: PlainStyle,
37 | style: Style,
38 | children: (interpolatedStyle: PlainStyle) => ReactElement,
39 | onRest?: () => void,
40 | };
41 |
42 | // === StaggeredMotion ===
43 | export type StaggeredProps = {
44 | defaultStyles?: Array,
45 | styles: (previousInterpolatedStyles: ?Array) => Array
115 |
116 |
117 |
118 |
119 |
Default: {stiffness: 170, damping: 26}
120 |
Drag a circle to see the differences in animation behavior
121 |
122 |
123 |
124 |
125 |
126 |