Skip to content
This repository has been archived by the owner on Sep 1, 2020. It is now read-only.

mdugue/react-dip

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

76 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

react-dip

Simple & declarative transition animations for React.

DISCLAIMER

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

Version Size Size Greenkeeper badge CodeFactor BCH compliance Known Vulnerabilities Build Status

Why?

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.

Table of contents

Installation

Using yarn or npm:

$ 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" />

Quick Start

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.

Edit react-dip Quick Start

Props

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.

dipId

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.

children

React Element | Required unless using render-prop!

Content that is rendered as part of that Dip.

render

function({ref: function(), styles: {}}) | Required unless using children-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 />.

duration

number | optional, defaults to 200

Time in milliseconds the animation will take when transitioning to this dip.

easing

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).

element

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.

optInCssStyles

Array(string) | optional, defaults to an empty array

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

className, id, style, aria-..., role, etc.

default react / HTML attributes | optional

Any provided standard attribute is passed to the child-container.

Polyfill

As some browsers do not support the Web Animations API, we recommend using the web-animations-js Polyfill.

  1. Install the dependency
$ yarn add web-animations-js

# or using npm
$ npm install --save web-animations-js
  1. include the dependency into your project.
import('web-animations-js') // We recommend dynamic imports to keep initial bundlesize small

Examples

How it works

Basic idea

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:

  1. registering the Component as a potential transition start point
  2. 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:

  1. we create a clone of the destination-element including it's size and position and append this clone on a proper Animation-DOM-Layer
  2. we calculate the css transforms to position the clone at the start position and scale it to the size of the start element
  3. we animate from 2. to 1., hiding the destination-Element whilst animating (currently via web-animations-api, which might change though)

Why a clone?

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.

Dip-WHAT???

No DEEEE-EYE-PEEE, just dip your taco into some tasty salsa. ๐ŸŒฎ

Browser Compatiblity

Chrome Firefox Safari Edge IE iOS
โœ… โœ… โœ…* โœ…* โœ…* โœ…*

* (requires polyfill)

Caveats (TODO)

  • transitioning to elements in scrolled lists (via browser back)
  • text can get distorted
  • styles from nested queries eg: animating h1 which is styled via section h1 (might be fixable a little bit, not sure if it is worth though, as it is somehow considered bad practices anyhow?)

Inspired by

Huge Thanks to

TODOs

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.

To be done before anouncing

  • 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

For some near future milestone

  • 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