Skip to content
Set of tools which helps you animate route / component transitions
TypeScript JavaScript Other
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode
example
src
.gitignore
README.md
babel.config.build.js
babel.config.js
jest.config.js
listdetail.gif
package.json
rollup.config.js
simple.gif
tsconfig.json
tslint.json
yarn.lock

README.md

Simple Demo

react-transpose

NPM

react-transpose is a set of tools which helps you animate route or component transitions. It also enables you to animate shared-element transitions.

This package makes use of the awesome library popmotion for it's animations. This means that under the hood it uses the same spring / tween / etc.. animations types, and since this is a react package, it has some strong simularities with react-pose. In a way, this package can be seen as an extension on top of react-pose.

Why not use PoseGroup?

While PoseGroup is excellent in most use-cases probably way more suffisticated, sometimes it's hard to handle route specific configuration. What if you wanted to animate the entering direction of a component based on the route it was comming from? That's where react-transpose comes in: for each transition-state (enter / exit) it gives you additional props for acting on specific route-transitions. On top of that this package provides tools to create so called shared-element transitions.

Install

npm install --save react-transpose

or

yarn add react-transpose

This package has a peer-dependency on popmotion, so make sure to install this package together with popmotion@^8.6.0. If your project is using popmotion's react-pose there is no need to install popmotion since react-pose already has a dependency on it.

Usage

Simple demo

Simple Demo

Edit react-transpose simple demo

import * as React from "react";
import { SwitchGroup, Stage, transposedShared } from "react-transpose";

const Box = transposedShared.div({
  // declare which style-props should be animated
  animationProps: ["width", "height", "backgroundColor"],
  // standard react-pose transition config
  transition: {
    // animation type (spring / tween / keyframes / etc...)
    type: "tween",
    duration: 300
  },
  // for identifying shared elements
  sharedKey: "box"
});

function App() {
  const [currentStage, setStage] = React.useState("route-a");

  // toggle stage every second
  React.useEffect(() => {
    const id = setInterval(() => {
      setStage(currentStage =>
        currentStage === "route-a" ? "route-b" : "route-a"
      );
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return (
    <div style={{ position: "relative" }}>
      <SwitchGroup stage={currentStage}>
        <Stage
          stage="route-a"
          render={() => (
            <div style={{ position: "absolute" }}>
              <Box
                style={{ width: 100, height: 100, backgroundColor: "blue" }}
              />
            </div>
          )}
        />
        <Stage
          stage="route-b"
          render={() => (
            <div style={{ position: "absolute" }}>
              <Box
                style={{ width: 200, height: 200, backgroundColor: "red" }}
              />
            </div>
          )}
        />
      </SwitchGroup>
    </div>
  );
}

Other demo's

API

SwitchGroup

It's main purpose is to render the right <Stage /> out of one or multiple stages (paths/routes) given a current stage. It has a similar function to Switch in react-router.

Prop type required Description
children ReactNode[] yes Must be one or more <Stage /> elements
stage string yes the current stage
setStage (stage: string) => void no Use in combination with useStage to set a stage from deeper within the tree

example

import { SwitchGroup, Stage } from "react-transpose";

function App() {
  const [currentStage, setStage] = React.useState("a");

  return (
    <SwitchGroup stage={currentStage} setStage={setStage}>
      <Stage stage="a" render={() => <div>A</div>} />
      <Stage stage="b" render={() => <div>B</div>} />
    </SwitchGroup>
  );
}

Stage

Used to represent a stage or path, much like a <Route /> in react-router. Use either the render or the component prop.

Prop type required Description
stage string yes
render () => ReactElement no
component Component no

useStage

Helper hook to set a stage from deeper within the tree. Note: the 'setStage' prop must be given to the <SwitchGroup /> component.

import { useStage } from "react-transpose";

function Route() {
  const { setStage } = useStage();

  return <button onClick={() => setStage("next")}>Goto next route</button>;
}

Group

Similar to <SwitchGroup />, but lets you handle the routing mechanism. Useful in combination with react-router for example.

Prop type required Description
currentPath string yes path of current location
children ReactElement yes Can only be one child at a time

example with react-router

function App({ location }) {
  return (
    <Group currentPath={location.pathname}>
      <Switch location={location}>
        <Route exact path={"/"} component={Home} />
        <Route exact path={"/about"} component={About} />
      </Switch>
    </Group>
  );
}

transposed

Function which creates components with animation capabilities. As with libraries as styled-components and react-pose, you can either pass in a existing component (don't forgot to forward the ref to a dom-node), or a tag name. So these all work:

// existing component
import Box from "./Box";
const AnimatedBox = transposed(Box)({});

// argument string
const AnimatedBox = transposed("div")({});

// helper method
const AnimatedBox = transposed.div({});

Config

Prop type required Description
animateFirst boolean no defaults to false. Determines whether to animate entering the first route
enter `object (props: TransitionProps) => object` yes
exit `object (props: TransitionProps) => object` yes

The enter and exit prop takes either a function returning an object, or a plain object. It is almost the same as how react-pose does it. For example:

const Box = transposed.div({
  enter: {
    // style props...
    opacity: 1,
    x: 0,
    backgroundColor: 'red',
    transition: { // info about transition
      type: "spring"
      stiffness: 200, // specfic spring prop
      damping: 25,  // specfic spring prop
      delay: 500 // different from react-pose
    }
  },
  exit: {
    opacity: 0,
    x: 100,
    backgroundColor: 'blue',
    transition: {
      type: "tween"
      duration: 400 // specific tween prop
    }
  }
})

When passing a function, you'll get a object with these properties

Prop Type Description
path { from: string: to: string }
props object Provides access to the wrapped component's props
direction `"in" "out"`

For example:

const Box = transposed.div({
  enter: ({ props }) => ({
    opacity: 1,
    x: 0,
    transition: {
      type: "spring",
      delay: props.index * 100 // staggering effect
    }
  }),
  exit: ({ path, direction }) => ({
    opacity: 0,
    // animate differently based on direction and path.from
    x: path.from === "a" && direction === "in" ? -100 : 300
  })
});

transposedShared

Quite similar to transposed, but it's main purpose is to animate shared-element transitions.

Config

Prop type required Description
sharedKey `string (props: object) => string` yes
animationProps string[] yes style-props to be animated
transition `object (props: TransitionProps) => object` yes
whenNotShared { enter: object; exit: object; animatedFirst: boolean } no see below

Example:

Edit react-transposed transposedShared-example

const Box = transposedShared.div({
  sharedKey: props => props.id,
  animationProps: ["x", "y", "width", "height"],
  transition: {
    type: "spring",
    stiffness: 400,
    damping: 30
  },
  // (optional) practically same as transposed config
  whenNotShared: {
    enter: { opacity: 1 },
    exit: { opacity: 0 }
  }
});

const BoxContext = React.createContext();

function List() {
  const { gotoDetail } = React.useContext(BoxContext);

  return (
    <div>
      <Box id="1" onClick={() => gotoDetail("1")} />
      <Box id="2" onClick={() => gotoDetail("2")} />
      <Box id="3" onClick={() => gotoDetail("3")} />
      <Box id="4" onClick={() => gotoDetail("4")} />
    </div>
  );
}

function Detail() {
  const { gotoList, selectedBox } = React.useContext(BoxContext);

  return (
    <>
      <button onClick={gotoList}>Back</button>
      <Box id={selectedBox} />
    </>
  );
}

function App() {
  const [state, setState] = React.useState({
    stage: "list",
    selectedBox: null
  });

  const payload = React.useMemo(
    () => ({
      ...state,
      gotoDetail: box => setState({ stage: "detail", selectedBox: box }),
      gotoList: box => setState({ stage: "list", selectedBox: null })
    }),
    [state]
  );

  return (
    <BoxContext.Provider value={payload}>
      <SwitchGroup stage={state.stage}>
        <Stage stage="list" component={List} />
        <Stage stage="detail" component={Detail} />
      </SwitchGroup>
    </BoxContext.Provider>
  );
}

License

MIT © everweij

You can’t perform that action at this time.