├── .babelrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── overdrive.test.js └── prefix.test.js ├── assets ├── browserstack-logo.png ├── nextgram-overdrive.gif ├── overdrive.gif └── rr-overdrive.gif ├── demos ├── nextgram │ ├── .babelrc │ ├── README.md │ ├── components │ │ ├── frame.js │ │ └── modal.js │ ├── package.json │ └── pages │ │ ├── index.js │ │ ├── photo.js │ │ └── profile.js ├── nextjs │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── pages │ │ ├── _document.js │ │ ├── character.js │ │ └── index.js │ └── static │ │ ├── bender.jpg │ │ ├── fry.jpg │ │ ├── leela.jpg │ │ └── zoidberg.png ├── react-router-v4 │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── index.css │ │ └── index.js └── website │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── pages │ ├── _document.js │ ├── character.js │ └── index.js │ └── static │ ├── bender.jpg │ ├── fry.jpg │ ├── leela.jpg │ └── zoidberg.png ├── index.d.ts ├── lib ├── Overdrive.js ├── Overdrive.js.map └── Overdrive.min.js ├── package.json ├── src ├── index.js ├── overdrive.js └── prefix.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "es2017" 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "transform-decorators-legacy", 10 | "transform-class-properties", 11 | "transform-object-rest-spread" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Remove some common IDE working directories 30 | .idea 31 | .vscode 32 | 33 | # nextjs - demo 34 | .next -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.10 (May 16, 2018) 2 | 3 | * Added support for custom easing ([@pizza3](https://github.com/pizza3) in [#46](https://github.com/berzniz/react-overdrive/pull/46)) 4 | 5 | ## 0.0.9 (October 3, 2017) 6 | 7 | * React 16 support ([@knpwrs](https://github.com/knpwrs) in [#37](https://github.com/berzniz/react-overdrive/pull/37)) 8 | 9 | ## 0.0.8 (July 23, 2017) 10 | 11 | * Added TypeScript definitions ([@sbking](https://github.com/sbking) in [#33](https://github.com/berzniz/react-overdrive/pull/33)) 12 | 13 | ## 0.0.7 (May 29, 2017) 14 | 15 | * Added AnimationEnd Event ([@vasco-santos](https://github.com/vasco-santos) in [#28](https://github.com/berzniz/react-overdrive/pull/28)) 16 | 17 | ## 0.0.6 (May 20, 2017) 18 | 19 | * Upgrade React and use `prop-types` npm package ([@na-ji](https://github.com/na-ji) in [#26](https://github.com/berzniz/react-overdrive/pull/26)) 20 | 21 | ## 0.0.5 (April 16, 2017) 22 | 23 | * Verify `` only takes a single children (`only children`) ([@yujiangshui](https://github.com/yujiangshui) in [#23](https://github.com/berzniz/react-overdrive/pull/23)) 24 | * Add `element` prop ([@yujiangshui](https://github.com/yujiangshui) in [#23](https://github.com/berzniz/react-overdrive/pull/23)) 25 | * Added `nextgram` demo 26 | 27 | ## 0.0.4 (March 24, 2017) 28 | 29 | * Add style prefix function to support older Safari ([@abstracthat](https://github.com/abstracthat) in [#15](https://github.com/berzniz/react-overdrive/pull/15)) 30 | * README.md fixes ([@lex111](https://github.com/lex111) in [#16](https://github.com/berzniz/react-overdrive/pull/16)) 31 | 32 | ## 0.0.3 (March 22, 2017) 33 | 34 | * Fix components that use context ([@Anujan](https://github.com/Anujan) in [#12](https://github.com/berzniz/react-overdrive/pull/12)) 35 | 36 | ## 0.0.2 (March 21, 2017) 37 | 38 | * Use window.pageYOffset for better cross browser support [#8](https://github.com/berzniz/react-overdrive/pull/8)) 39 | * Use defaultProps for duration ([@diegomura](https://github.com/diegomura) in [#3](https://github.com/berzniz/react-overdrive/pull/3)) 40 | * README.md improvements ([@timbuckley](https://github.com/timbuckley) in [#4](https://github.com/berzniz/react-overdrive/pull/4)) 41 | 42 | ## 0.0.1 (March 6, 2017) 43 | 44 | * Initial public release 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Tal Bereznitskey 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-overdrive 2 | Super easy magic-move transitions for React apps. 3 | 4 | ## Demos 5 | 6 | 1. [Page transitions](https://overdrive-demo.now.sh) 7 | 8 | ![Overdrive Demo](assets/overdrive.gif "Demo") 9 | 10 | 2. [Image Gallery with next.js](https://nextgram-overdrive.now.sh) 11 | 12 | ![Overdrive Demo](assets/nextgram-overdrive.gif "Demo") 13 | 14 | 3. [With React Router](https://overdrive-rr4.now.sh) 15 | 16 | ![Overdrive Demo](assets/rr-overdrive.gif "Demo") 17 | 18 | ## Install 19 | 20 | ``` 21 | $ npm install react-overdrive --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### Example with routing 27 | 28 | Wrap any element (not just images) in a `` component. Add the same `id` to create a transition between the elements. 29 | 30 | On `page1.js`: 31 | ```js 32 | import Overdrive from 'react-overdrive' 33 | 34 | const pageA = (props) => ( 35 |
36 |

Page A

37 | 38 | 39 | 40 |
41 | ); 42 | ``` 43 | 44 | On `page2.js`: 45 | ```js 46 | import Overdrive from 'react-overdrive' 47 | 48 | const pageB = (props) => ( 49 |
50 |

Page B

51 | 52 | 53 | 54 |
55 | ); 56 | ``` 57 | 58 | Now route between the pages. 59 | 60 | ### Example without routing 61 | 62 | On `page.js`: 63 | ```js 64 | import Overdrive from 'react-overdrive' 65 | 66 | const page = (props) => ( 67 |
68 | {props.loading && } 69 | {!props.loading && } 70 |
71 | ); 72 | ``` 73 | 74 | ## API 75 | 76 | | Prop | Description | Default Value | 77 | |----------------|------------------------------------------------------------------------------------------------------------------------------|---------------| 78 | | id | Required. A unique string to identify the component. | | 79 | | element | Wrapping element type. | 'div' | 80 | | duration | Animation duration (in milliseconds). | 200 | 81 | | easing | Animation easing function. | '' | 82 | | animationDelay | Add delay of calculating the mounted component position. Setting to `1` usually helps avoiding issues with window scrolling. | null | 83 | | onAnimationEnd | Event dispatched when the animation has finished. | null | 84 | 85 | ## How does it work? 86 | 87 | A transition is made when an `` component is unmounted and another `` is mounted not later than 100ms. 88 | 89 | The transition is made by cloning the unmounted and mounted components, adding them with `absolute` position and CSS transformed from the source to the target position. 90 | 91 | ## Sponsors 92 | 93 | Thanks to the following companies for generously providing their services/products to help improve this project: 94 | 95 | 96 | 97 | Thanks to [BrowserStack](https://browserstack.com/) for providing cross-browser testing. 98 | 99 | ## Who made this? 100 | 101 | Tal Bereznitskey. Find me on Twitter as [@ketacode](https://twitter.com/ketacode). 102 | -------------------------------------------------------------------------------- /__tests__/overdrive.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | 3 | import React from 'react' 4 | import Overdrive from '../src/overdrive' 5 | import renderer from 'react-test-renderer' 6 | 7 | describe('Overdrive', () => { 8 | it('should use props element to render', () => { 9 | const component = renderer.create( 10 | test 11 | ) 12 | let tree = component.toJSON() 13 | expect(tree.type).toEqual('h1') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /__tests__/prefix.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | 3 | import prefix from '../src/prefix' 4 | 5 | describe('prefix', () => { 6 | it('should return an object on empty input', () => { 7 | expect(prefix()).toEqual({}) 8 | expect(prefix(null)).toEqual({}) 9 | expect(prefix({})).toEqual({}) 10 | }) 11 | 12 | it('should add webkit prefix to correct properties', () => { 13 | expect(prefix({ 14 | width: '50px', 15 | transform: 1, 16 | transformOrigin: 2, 17 | transition: 3 18 | })).toEqual({ 19 | width: '50px', 20 | WebkitTransform: 1, 21 | WebkitTransformOrigin: 2, 22 | WebkitTransition: 3, 23 | transform: 1, 24 | transformOrigin: 2, 25 | transition: 3 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /assets/browserstack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berzniz/react-overdrive/07666391e5d2f7e46685f3efeb82fa66dc0c32d8/assets/browserstack-logo.png -------------------------------------------------------------------------------- /assets/nextgram-overdrive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berzniz/react-overdrive/07666391e5d2f7e46685f3efeb82fa66dc0c32d8/assets/nextgram-overdrive.gif -------------------------------------------------------------------------------- /assets/overdrive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berzniz/react-overdrive/07666391e5d2f7e46685f3efeb82fa66dc0c32d8/assets/overdrive.gif -------------------------------------------------------------------------------- /assets/rr-overdrive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berzniz/react-overdrive/07666391e5d2f7e46685f3efeb82fa66dc0c32d8/assets/rr-overdrive.gif -------------------------------------------------------------------------------- /demos/nextgram/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } -------------------------------------------------------------------------------- /demos/nextgram/README.md: -------------------------------------------------------------------------------- 1 | 2 | # NextGram 3 | 4 | Cloned from https://github.com/zeit/nextgram and added `` for transitions. 5 | 6 | # Installation 7 | 8 | $ npm install 9 | 10 | # Running the app 11 | 12 | $ npm run dev 13 | -------------------------------------------------------------------------------- /demos/nextgram/components/frame.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import Overdrive from 'react-overdrive' 4 | 5 | export default ({ id }) => ( 6 | 7 |
8 | 9 |
10 | {id} 11 |
12 | 13 |
14 |
    15 |
  • 16 | @nkzawa 17 | - Great photo! 18 |
  • 19 |
20 |
21 | 22 | 61 |
62 |
63 | ) 64 | -------------------------------------------------------------------------------- /demos/nextgram/components/modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Photo from './frame' 3 | 4 | export default class extends React.Component { 5 | dismiss (e) { 6 | if (this._shim === e.target || 7 | this._photoWrap === e.target) { 8 | if (this.props.onDismiss) { 9 | this.props.onDismiss() 10 | } 11 | } 12 | } 13 | 14 | render () { 15 | return ( 16 |
(this._shim = el)} className='shim' onClick={(e) => this.dismiss(e)}> 17 |
(this._photoWrap = el)} className='photo'> 18 | 19 |
20 | 38 |
39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demos/nextgram/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextgram", 3 | "private": true, 4 | "dependencies": { 5 | "next": "^9.3.3", 6 | "react": "^16.0.0", 7 | "react-dom": "^16.0.0", 8 | "react-overdrive": "^0.0.7" 9 | }, 10 | "devDependencies": { 11 | "babel-eslint": "7.0.0", 12 | "standard": "8.4.0" 13 | }, 14 | "standard": { 15 | "parser": "babel-eslint" 16 | }, 17 | "scripts": { 18 | "dev": "next", 19 | "build": "next build", 20 | "start": "NODE_ENV=production next start", 21 | "lint": "standard" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/nextgram/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Router from 'next/router' 3 | import Overdrive from 'react-overdrive' 4 | import Modal from '../components/modal' 5 | 6 | export default class extends React.Component { 7 | static getInitialProps () { 8 | return { 9 | photos: new Array(15).fill(0).map((v, k) => k + 1) 10 | } 11 | } 12 | 13 | constructor (props) { 14 | super(props) 15 | this.onKeyDown = this.onKeyDown.bind(this) 16 | } 17 | 18 | // handling escape close 19 | componentDidMount () { 20 | document.addEventListener('keydown', this.onKeyDown) 21 | } 22 | 23 | componentWillUnmount () { 24 | document.removeEventListener('keydown', this.onKeyDown) 25 | } 26 | 27 | onKeyDown (e) { 28 | if (!this.props.url.query.photoId) return 29 | if (e.keyCode === 27) { 30 | this.props.url.back() 31 | } 32 | } 33 | 34 | dismissModal () { 35 | Router.push('/') 36 | } 37 | 38 | showPhoto (e, id) { 39 | e.preventDefault() 40 | Router.push(`/?photoId=${id}`, `/photo?id=${id}`) 41 | } 42 | 43 | render () { 44 | const { url, photos } = this.props 45 | 46 | return ( 47 |
48 | { 49 | url.query.photoId && 50 | this.dismissModal()} 53 | /> 54 | } 55 | { 56 | photos 57 | .filter((id) => (id !== url.query.photoId)) 58 | .map((id) => ( 59 | 60 | 69 | 70 | )) 71 | } 72 | 101 |
102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /demos/nextgram/pages/photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Photo from '../components/frame' 3 | 4 | export default ({ url: { query: { id } } }) => ( 5 |
6 |
7 | 8 |
9 | 21 |
22 | ) 23 | -------------------------------------------------------------------------------- /demos/nextgram/pages/profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default ({ url: { query: { id } } }) => ( 4 |
5 |

6 | User profile: 7 | {' '} 8 | {id} 9 |

10 | 23 |
24 | ) 25 | -------------------------------------------------------------------------------- /demos/nextjs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } -------------------------------------------------------------------------------- /demos/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # next.js demo 2 | 3 | Run demo: 4 | ``` 5 | npm install 6 | npm run dev 7 | // open http://localhost:3000 8 | ``` -------------------------------------------------------------------------------- /demos/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-overdrive-nextjs-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "keywords": [], 11 | "author": "Tal Bereznitskey (http://berzniz.com/)", 12 | "license": "ISC", 13 | "dependencies": { 14 | "glamor": "^2.20.24", 15 | "next": "^9.3.3", 16 | "react": "^16.0.0", 17 | "react-dom": "^16.0.0", 18 | "react-overdrive": "^0.0.7" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demos/nextjs/pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, {Head, Main, NextScript} from 'next/document' 2 | import {renderStatic} from 'glamor/server' 3 | 4 | export default class MyDocument extends Document { 5 | static async getInitialProps ({renderPage}) { 6 | const page = renderPage() 7 | const styles = renderStatic(() => page.html) 8 | return {...page, ...styles} 9 | } 10 | 11 | constructor (props) { 12 | super(props) 13 | const {__NEXT_DATA__, ids} = props 14 | if (ids) { 15 | __NEXT_DATA__.ids = this.props.ids 16 | } 17 | } 18 | 19 | render () { 20 | return ( 21 | 22 | 23 | Demo 24 | 25 |