New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staggered unmounting animation in TransitionMotion Demo #220

Open
webyak opened this Issue Oct 17, 2015 · 10 comments

Comments

Projects
None yet
7 participants
@webyak

webyak commented Oct 17, 2015

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" =)

@chenglou

This comment has been minimized.

Owner

chenglou commented Oct 17, 2015

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

@webyak

This comment has been minimized.

webyak commented Oct 18, 2015

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! : )

@webyak

This comment has been minimized.

webyak commented Oct 18, 2015

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

@arush

This comment has been minimized.

arush commented Oct 18, 2015

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

@webyak

This comment has been minimized.

webyak commented Oct 18, 2015

@arush Nope, TransitionMotion 😁

@webyak

This comment has been minimized.

webyak commented Oct 18, 2015

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.

@iandoe

This comment has been minimized.

iandoe commented Oct 23, 2015

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 ?

@nkbt

This comment has been minimized.

Collaborator

nkbt commented Oct 23, 2015

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

@tptee

This comment has been minimized.

tptee commented Jun 13, 2016

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

@mlewando

This comment has been minimized.

mlewando commented Dec 27, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment