Simple & declarative transition animations for React.
THIS IS EARLY WORK IN PROGRESS AND NOT "RELEASED TO PUBLIC" YET, THOUGH THIS SHOULD HAPPEN ANYTIME SOON.
FEEL FREE TO WATCH ๐, STAR โญ OR CONTACT ME TO BE NOTIFIED ABOUT PROGRESS.
TODO: Video / gif here
Ever wanted to implement one of those incredible designs you find on dribble or Muzli where one element beautifully transforms into another upon page transition? And then realized "But I want my code to stay statefull, decoupled, scalable, declarative", so you ended up with regular "hard cuts" instead? โ To me this happend very very often!
react-dip
solves this by providing animated transisions in an effortless way, that just worksTM, by using the FLIP technique.
- Installation
- Quick Start
- Props
- Polyfill
- Examples
- How it works
- Dip-WHAT???
- Browser Compatiblity
- Caveats (TODO)
- Inspired by
- Huge Thanks to
- TODOs
$ yarn add react-dip
# or using npm
$ npm install --save react-dip
Using a module bundler like webpack or parcel, import your depency just as any other:
// Using ES6 modules
import Dip from 'react-dip'
// or using CommonJS modules
var Dip = require('react-dip')
UMD builds are also available via unpkg:
<script src="https://unpkg.com/react-dip/dist/react-dip.umd.min.js" />
The API is as small as possible, almost everything is optional, so you see results immediately. Just wrap your Items in a Dip
import React, {Component} from 'react'
import Dip from 'react-dip'
function Component1() {
return (
<Dip dipId="quickStart" style={{background: 'red'}}>
some content
</Dip>
)
}
function Component2() {
return (
<Dip
dipId="quickStart"
style={{position: 'absolute', top: '100px', background: 'green'}}
>
some other content <br />
etc...
</Dip>
)
}
// use complex state here
// or a routing solution such as react-router
// or connect it to redux, or ustated
export default class MyStatefulParent extends Component {
state = {currentStep: 0}
toggleState = () =>
this.setState(state => ({
currentStep: (state.currentStep + 1) % 2,
}))
render() {
return (
<section>
<h1> Quick Start example </h1>
<button onClick={this.toggleState}>toggle me</button>
{this.state.currentStep === 0 ? <Component1 /> : <Component2 />}
</section>
)
}
}
Note: Using inline styles
as well as absolute
positioning is usually not considered a good way and is applied here for the sake of simplicity.
You can use any type CSS
or CSS-in-JS
styling and fluid
/ flex
/ grid
layout.
The API surface is intetended to be as simple as possible. Only dipId
and children
(or render
) are required props. The rest is optional and helps you fine tuning your animations.
string
| Required!
The id
that groups two different dip elements. React-dip will only create animated transitions between to Elements with the same dipId
, consider them as potential from- and to-hints.
React Element
| Required unless usingrender
-prop!
Content that is rendered as part of that Dip
.
function({ref: function(), styles: {}})
| Required unless usingchildren
-prop!
Function that should return the content that is rendered as part of that Dip
. Allows for more advanced pattern and skips the wrapping Element. See render prop for further details.
Warning: <Dip render>
takes precedence over <Dip children>
so donโt use both in the same <Dip />
.
number
| optional, defaults to200
Time in milliseconds the animation will take when transitioning to this dip.
string
| optional, defaults to"ease-out"
Specifies the desired timing function. Accepts the pre-defined values linear
, ease
, ease-in
, ease-out
, and ease-in-out
, or a custom cubic-bezier
value like cubic-bezier(0.42, 0, 0.58, 1)
.
string
| optional, defaults to"div"
Specify the desired HTML-tag here (eg. <Dip element="li">
) in case you don't want your children wrapped in a div
.
Array(string)
| optional, defaults to an emptyarray
By default react-dip
will morph your components only regarding their sizes and positions using css transforms
which is usually a good default regarding performance.
In case you want to morph more css properties you can specify them here, such as optInCssStyles={["borderRadius", "backgroundColor"]}
. optional
default
react
/HTML attributes
| optional
Any provided standard attribute is passed to the child-container.
As some browsers do not support the Web Animations API, we recommend using the web-animations-js Polyfill.
- Install the dependency
$ yarn add web-animations-js
# or using npm
$ npm install --save web-animations-js
- include the dependency into your project.
import('web-animations-js') // We recommend dynamic imports to keep initial bundlesize small
The basic idea is the following: Every candidate for an animated transition is wrapped in a <Dip />
Container,
whereas potential start and destination elements get grouped via the same dipId
property. Say, if you want to animnate a users name from a list to a detail view
you'd wrap the name in both views in a <Dip dipId={
name-${username}}>
Container.
Wrapping the Components in a Dip
-Container does a couple of things:
- registering the Component as a potential transition start point
- checking for existing registered elements
If the check for existing registered elements was successfull, we kick in the animation logic, based on the FLIP-technique:
- we create a clone of the destination-element including it's size and position and append this clone on a proper Animation-DOM-Layer
- we calculate the css transforms to position the clone at the start position and scale it to the size of the start element
- we animate from 2. to 1., hiding the destination-Element whilst animating (currently via web-animations-api, which might change though)
Whilst a clone has some downsides, such as creating new DOM-Elements just for animating and possible variations in the styling if nested css selectors were used
it gives us more freedom eg. when animating to an element which is inside of a container with overflow: hidden
.
No DEEEE-EYE-PEEE, just dip your taco into some tasty salsa. ๐ฎ
Chrome | Firefox | Safari | Edge | IE | iOS |
---|---|---|---|---|---|
โ | โ | โ * | โ * | โ * | โ * |
- transitioning to elements in scrolled lists (via browser back)
- text can get distorted
- styles from nested queries eg: animating
h1
which is styled viasection h1
(might be fixable a little bit, not sure if it is worth though, as it is somehow considered bad practices anyhow?)
- https://github.com/joshwcomeau/react-flip-move
- https://github.com/fram-x/FluidTransitions/
- https://css-tricks.com/native-like-animations-for-page-transitions-on-the-web/
- Kent C. Dodds for his inspiring work, such as kcd-scripts
- Ives van Horne amongst other for CodeSandbox
There are tons of ideas for improving react-dip
such as adding fine grained control to your transitions, but the primary goal will stay to keep the API as symple as possible.
- add chapter about polyfilling
- render props (of course)
- add support for custom timing functions
- add complex examples with renderProps, routing etc.
- add possibility of declaring alternative components that are shown whilst animating
- add recipie for transitioning lowres- to highres-images
- export types for flow and typescript
- add contributing guide lines
- add error handling for props
- add error handling for refs
- move animation to proper element, allowing for parents with
overflow: hidden
and avoiding z-index issues - add Component for Fading in non-dip Elements, after / while a Dip-transions
- add support for staggering
- add real tests
- export types for flowtyped and typescript
- add optional
placeholder
component that is shown whilst animating - add documentation, (maybe even abstractions) for image transitions from low-res to high-res
- make animation-layer custmizable, so regular items can be in front of them (
z
-wise) - investigate possible optimizations for the scrolling issues