React-motion: Staggered unmounting animation in TransitionMotion Demo

Created on 17 Oct 2015  路  10Comments  路  Source: chenglou/react-motion

I can't get it fully working to animate both mounting & unmounting components staggered within TransitionMotion.

It would be awesome to have a demo because you also mentioned it in the docs under "TransitionMotion#styles" =)

Most helpful comment

I'm running into a brick wall with this as well. Is there any existing example code for staggered animations with TransitionMotion?

All 10 comments

Can you be more precise please? E.g. a code snippet would help =)

This is the working part I got so far. But only for the staggered mounting animation. https://d.pr/v/10Gkd

  _getStyles (prevStyles) {
    const {items} = this.props;
    const itemHeight = 84;
    let destinationStyles = {};

    if (!prevStyles) {
      // reverse because the last item should lead the staggering.
      items.reverse().forEach(flight => {
        destinationStyles[item.id] = {
          opacity: spring(0),
          y: spring(-200),
          flight
        };
      });

      return destinationStyles;
    }

    Object.keys(prevStyles).forEach((key, i, keysArr) => {
      const {items} = prevStyles[key];
      let destinationY = 0;

      if (i === 0) {
        // fixed position for the item that leads the staggering.
        destinationY = (keysArr.length - 1) * itemHeight;
      } else {
        // every other item follows the item before them.
        const itemBefore = prevStyles[keysArr[i - 1]];
        destinationY = itemBefore.y.val - itemHeight;
      }

      destinationStyles[item.id] = {
        opacity: spring(1),
        y: spring(destinationY, [300, 30]),
        flight
      };
    });

    return destinationStyles;
  }

I'm now looking to animate unmounts too. Just instead of sliding in they should slide out down.

Now when I'm going to differentiate between the leading mounting and unmounting component to do 2 independent staggered animations all within getStyles, I think this might get ugly.
Also it's strange that I'm not using willEnter/willLeave so far so could I be better off using StaggeredMotion 2 times?

As you can see I'm really unsure here, would be awesome if you could point me into the right direction! : )

Update: Got it working, will post the code soon!

@webyak would love to see it, did you end up using StaggeredMotion?

@arush Nope, TransitionMotion 馃榿

This is my solution. I think it could be improved quite a lot. Do you guys have any idea on how to make this more simple?

// ==================================================
// OutboundList
// ----
// A list of outbound flights.
// ==================================================

import React from "react";
import {TransitionMotion, spring} from "react-motion";
import OutboundFlight from "./OutboundFlight";

// full css height of a single item.
const itemHeight = 84;

const OutboundList = React.createClass({
  getInitialState() {
    return {reversedFlights: []};
  },
  componentWillReceiveProps ({flights}) {
    this.setState({
      reversedFlights: flights.reverse()
    });
  },
  _getDestinationStyles (prevStyles) {
    const {reversedFlights} = this.state;
    let destinationStyles = {};

    // NOTE: calling props.flights.reverse() here messes things up,
    // aka being inaccurate.
    reversedFlights.forEach((flight, i) => {
      let beforeKey = "";
      // leading item is at the bottom.
      let destinationY = (reversedFlights.length - 1) * itemHeight;

      if (i !== 0) {
        beforeKey = reversedFlights[i - 1].id;
        const beforeStyle = destinationStyles[beforeKey];
        destinationY = Math.floor(beforeStyle.y.val - itemHeight);
      }

      destinationStyles[flight.id] = {
        opacity: spring(1),
        y: spring(destinationY),
        offset: i * itemHeight,
        beforeKey,
        flight
      };
    });

    // include additional styles from previous styles & override styles.
    if (prevStyles) {
      Object.keys(prevStyles).forEach(key => {
        const {beforeKey, flight, offset} = prevStyles[key];
        const beforeStyle = prevStyles[beforeKey];

        if (beforeStyle) {
          destinationStyles[key] = {
            opacity: spring(1),
            // subsequent styles follow the one before them.
            y: spring(Math.floor(beforeStyle.y.val - itemHeight)),
            offset,
            beforeKey,
            flight
          };
        }
      });
    }

    return destinationStyles;
  },
  _willEnter (key, {beforeKey, offset, flight}) {
    const enterY = -200 - offset;

    return {
      opacity: spring(0),
      y: spring(enterY),
      offset,
      beforeKey,
      flight
    };
  },
  _willLeave (key, {beforeKey, offset, flight}) {
    const leaveY = 600 - offset;

    return {
      opacity: spring(0),
      y: spring(leaveY),
      offset,
      beforeKey,
      flight
    };
  },
  render() {
    return (
      <div className="searchresults-list searchresults-list--outbound">
        <TransitionMotion
          styles={this._getDestinationStyles}
          willEnter={this._willEnter}
          willLeave={this._willLeave}>
          {interpolatedStyles =>
            <ul className="searchresults-flightList">
              {Object.keys(interpolatedStyles).map(key => {
                const {opacity, y, flight} = interpolatedStyles[key];
                return (
                  <li
                    key={key}
                    style={{
                      opacity,
                      WebkitTransform: `translate3d(0, ${y}px, 0)`,
                      transform: `translate3d(0, ${y}px, 0)`
                    }}>
                    <OutboundFlight {...flight}></OutboundFlight>
                  </li>
                );
              })}
            </ul>
          }
        </TransitionMotion>
      </div>
    );
  }
});

export default OutboundList;

Note: You probably want to animate opacity staggered too and change the default spring config.

I am having the same use case, ii want to animate the left and opacity style properties on every item in a list on mount in a staggered effect. Kind of struggling wrapping my head around it. Same as @webyak i thought using <StaggeredMotion> and willEnter would work but willEnter does not fire on initial mount, only when something is entering the list. Your solution looks smart @webyak but is there any way to implement this use case in the API @chenglou or am i missing something ?

@iandoe @webyak you can check sources of react-collapse, that might help.

I'm running into a brick wall with this as well. Is there any existing example code for staggered animations with TransitionMotion?

I too had this problem, but I managed to create more or less working example. You can check out the example here: https://github.com/mlewando/staggered-unmount-mount-react-motion

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AlastairTaft picture AlastairTaft  路  4Comments

cloudfroster picture cloudfroster  路  4Comments

sdras picture sdras  路  10Comments

codejunkienick picture codejunkienick  路  8Comments

kujon picture kujon  路  3Comments