5

I have an animation that works great moving items in from the right and off to the left. I want to do the opposite when the back button is clicked, similar to popping a View off the navigation stack in a mobile application. My code is below, how can I get the animation to run backwards when the button is clicked? I am using React 17.0.2 and react-spring 9.2.3 .

import React, { useState, useCallback, CSSProperties, useEffect } from "react";
import {
  useTransition,
  animated,
  AnimatedProps,
  useSpringRef,
} from "react-spring";

import styles from "../styles/components/carousel.module.css";

const Carousel: React.FC = () => {
  const [index, set] = useState(0);
  const onClick = useCallback(() => set((state) => (state + 1) % 6), []);
  const transRef = useSpringRef();
  const transitions = useTransition(index, {
    ref: transRef,
    keys: null,
    from: { opacity: 0, transform: "translate3d(100%,0,0)" },
    enter: { opacity: 1, transform: "translate3d(0%,0,0)" },
    leave: { opacity: 0, transform: "translate3d(-50%,0,0)" },
  });

  useEffect(() => {
    transRef.start();
  }, [index, transRef]);

  const pageContent: { text: string; color: string }[] = [
    { text: "one", color: "lightpink" },
    { text: "two", color: "lightblue" },
    { text: "three", color: "lightgreen" },
  ];

  const pages: ((
    props: AnimatedProps<{ style: CSSProperties }>
  ) => React.ReactElement)[] = pageContent.map((obj) => {
    return function createAnimatedDiv({ style }) {
      return (
        <animated.div style={{ ...style, background: obj.color }}>
          {obj.text}
        </animated.div>
      );
    };
  });

  const onBackClicked = useCallback(() => set((state) => (state - 1) % 6), []);

  return (
    <>
      <button onClick={onBackClicked}>Back</button>
      <div className={`flex fill ${styles.container}`} onClick={onClick}>
        {transitions((style, i) => {
          const Page = pages[i];
          return <Page style={style} />;
        })}
      </div>
    </>
  );
};

export default Carousel;
Josh
  • 2,547
  • 3
  • 13
  • 28

1 Answers1

3

First of all you need to know the an animation direction. For this purpose you can use usePrevious hook and comparison of current and previous index. Then, basing on direction you can return different transition config.

type Direction = 'left' | 'right' | null;

// helpers

const getDirection = (index: number, previousIndex: number): Direction => {
  if (index === previousIndex) {
    return null;
  }

  return index > previousIndex ? 'right' : 'left';
};

const getTransitionConfig = (direction: Direction): Parameters<typeof useTransition>[1] => {
  if (direction === 'right') {
    return {
      from: { opacity: 0, left: -300 },
      enter: { opacity: 1, left: 0 },
      leave: { opacity: 0, left: 300 },
    };
  }

  if (direction === 'left') {
    return {
      from: { opacity: 0, left: 300 },
      enter: { opacity: 1, left: 0 },
      leave: { opacity: 0, left: -300 },
    };
  }

  return {};
};

// component

const [index, set] = useState(0);
const previousIndex = usePrevious(index);
const animationDirection = getDirection(index, previousIndex);

const transitionConfig = useMemo(() => ({
  ref: transRef,
  keys: null,
  ...getTransitionConfig(animationDirection)
}), [animationDirection, getTransitionConfig]);

const transitions = useTransition(index, transitionConfig);
// ...

For the sake of simplicity I've used left attribute. You can use transform, of course.

Yuriy Yakym
  • 3,616
  • 17
  • 30