Kevnz
7/22/2015 - 3:41 AM

A component for doing child transitions using Velocity.js

A component for doing child transitions using Velocity.js

// Velocity Animate
// ================
//
// Inspired by https://gist.github.com/tkafka/0d94c6ec94297bb67091.
//
// Extend so it supports:
//
// 1) A transition object in the same format as the ones in the library below,
// directly to the component. Example:
//
//     let customTransition = {
//       duration: 1500,
//       enter: {
//         scale: [ 0.5, 1 ],
//         opacity: [0, 1 ]
//       },
//       leave: {
//         scale: [ 1, 1.25 ],
//         opacity: [ 1, 0 ]
//       }
//     }
//     …
//     return <VelocityTransitionGroup transition={customTransition}>…
//
// 2) Transition objects accept queued transforms. Example:
//
//     let customTransition = {
//       duration: 1500,
//       enter: [ { scale: [ 0.5, 1 ] }, { opacity: [ 0, 1 ] } ],
//       leave: [ { scale: [ 1, 0.5 ] }, { opacity: [ 1, 0 ] } ]
//     }
//
// Note that for queued transitions, the duration parameter will be split among
// the queue, so that means the total duration of all transitions.

import React from 'react'
import Velocity from 'velocity-animate'
import Animate from 'rc-animate'

const transitions = {
  'slide-forward': {
    duration: 500,
    enter: {
      translateX: [ '0%', '100%' ]
    },
    leave: {
      translateX: [ '-100%', '0%' ]
    }
  },
  'slide-back': {
    duration: 500,
    enter: {
      translateX: [ '0%', '-100%' ]
    },
    leave: {
      translateX: [ '100%', '0%' ]
    }
  },
  'slideover-forward': {
    duration: 500,
    enter: {
      translateX: [ '0%', '100%' ],
      zIndex: [ 1, 1 ]
    },
    leave: {
      zIndex: [ 0, 0 ]
    }
  },
  'slideover-back': {
    duration: 500,
    enter: {
      zIndex: [ 0, 0 ]
    },
    leave: {
      translateX: [ '100%', '0%' ],
      zIndex: [ 1, 1 ]
    }
  },
  'default': {
    duration: 2000,
    enter: {
      opacity: [ 1, 0 ]
    },
    leave: {
      opacity: [ 0, 1 ]
    }
  }
}

let resolveTransition = function(transition) {
  if (typeof transition !== 'string') return transition
  return transitions[transition]
}

let validateTransition = function(transition) {
  if (typeof transition === 'string' && !transitions[transition]) {
    throw new Error(
      `VelocityAnimate: Transition ${transition} not found in library`
    )
  }
  if (
      typeof transition === 'object' &&
      (!transition.duration || !transition.enter || !transition.leave)
    ) {
    throw new Error(
      'VelocityAnimate: object missing duration, enter, or leave key'
    )
  }
}

export default class VelocityAnimate extends React.Component {
  getTransition() {
    validateTransition(this.props.transition)
    return resolveTransition(this.props.transition)
  }

  render() {
    return <Animate
      component={this.props.component}
      exclusive={this.props.exclusive}
      className={this.props.className}
      animation={{
        enter: this.animateEnter.bind(this),
        leave: this.animateLeave.bind(this)
      }}>
      {this.props.children}
    </Animate>
  }

  animateEnter(node, callback) {
    let transition = this.getTransition()
    if (!Array.isArray(transition.enter)) transition.enter = [transition.enter]

    transition.enter.forEach((transform, index) => {
      let options = { duration: transition.duration / transition.enter.length, queue: false }
      if (index === transition.enter.length - 1) options.complete = callback
      Velocity(node, transform, options)
    })
    return { stop: () => { Velocity(node, 'stop') }}
  }

  animateLeave(node, callback) {
    let transition = this.getTransition()
    if (!Array.isArray(transition.leave)) transition.leave = [transition.leave]

    transition.leave.forEach((transform, index) => {
      let options = { duration: transition.duration / transition.leave.length, queue: false }
      if (index === transition.leave.length - 1) options.complete = callback
      Velocity(node, transform, options)
    })
    return { stop: () => { Velocity(node, 'stop') }}
  }
}

VelocityAnimate.propTypes = {
  component: React.PropTypes.string,
  className: React.PropTypes.string,
  exclusive: React.PropTypes.bool,
  transition: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.object
  ])
}

VelocityAnimate.defaultProps = {
  component: 'div',
  exclusive: false,
  transition: 'default'
}