Live example

Framer Motion cross-fades the slightly rotating animals and moves the header/logo as a new animal is showcased.

Set-up Framer Motion

You add Framer Motion to your project using npm or yarn to get started.

npm install framer-motion

Import Framer Motion into your React component. There are a number of Framer Motion functions that can be imported, we will start with the motion component.

import { motion } from 'framer-motion'

Performing the animation is now as simple as wrapping it in a <motion.div> component and passing animation props to the motion div.

<motion.div  initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
    <span>This text will fade in when created in the DOM</span>
</motion.div>

Framer Motion animates the certain properties of the<motion.div> element from the initial state to the animate state. The example above fades in a <span>, you can perform many animations such as scaling, moving and rotating your elements.

Controlling the animation is handled by adding props to the <motion.div>. The transition prop is powerful here, controlling many animation properties:

  transition={{
    yoyo: Infinity,
    duration: 2,
    ease: "easeInOut"
  }}

This animation loops back and forth indefinitely, the duration is set in seconds (2 seconds in this example), and an easing has been applied to the effect.

Creating our banner

Our hero banner is designed to showcase some city wildlife, with each slide consisting of an icon, a header and three images. Framer Motion animates these as we change from one animal to the next.

Creating our scene data

First, we are going to get together all the assets we need for our banner and assemble them into a static banner. There are three images and a logo per animal, and we will group these in exported objects (eg. hedgehogScene) to group data together.

We can then import these into our main App.js file and use them in our banner.

import React from "react";

import ImageHolder from "./ImageHolder";
import IconHolder from "./IconHolder";

import { hedgehogScene, raccoonScene, squirrelScene } from "./scenes";

function App() {
  const currentScene = hedgehogScene;

  return (
    <div className="hero">
      <IconHolder icon={currentScene.icon} text={currentScene.text} />
      <ImageHolder
        img={currentScene.image1}
        className="animal-image animal-image__one"
      />
      <ImageHolder
        img={currentScene.image2}
        className="animal-image animal-image__two"
      />
      <ImageHolder
        img={currentScene.image3}
        className="animal-image animal-image__three"
      />
    </div>
  );
}

export default App;

We are assigning the hedgehog data to the current scene for the moment, we will cycle through the remaining scenes late on.

Source code | Live example

So far, so good, we have a static scene with the first of our scene data. Now, for animating!

Animate - motion.div

We are going to fade in the animals when they are brought into the DOM, and have them rotate a bit to give them some movement.

We wrap our image tag with the following motion.div element.

import React from "react";
import { motion  } from "framer-motion";

function ImageHolder({ img, className }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1, rotate: [-6, 0, 6] }}
      className={className ? className : undefined}
    >
      <img src={img.src} alt={img.alt} />
    </motion.div>
  );
}

export default ImageHolder;

This will fade each image in and rotate it from -6 degrees to 6 degrees. If you run the code, this happens only once, which is not ideal. We want the image to fade in only once, but rotate constantly back and forth. We control this with the transition prop.


import React from "react";
import { motion } from "framer-motion";

const TRANSITION_TIME_OPACITY_S = 1;
const TRANSITION_TIME_ROTATE_S = 2;

function ImageHolder({ img, className }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1, rotate: [-6, 0, 6] }}
      transition={{
        duration: TRANSITION_TIME_OPACITY_S,
        rotate: { yoyo: Infinity, duration: TRANSITION_TIME_ROTATE_S },
      }}
      className={className ? className : undefined}
    >
      <img src={img.src} alt={img.alt} />
    </motion.div>
  );
}

export default ImageHolder;

We also control the duration of the animation using the transform. The first duration property will apply to all transitions in the animation. If we want to control a specific animation property, we can specify it with the appropriate key like we are doing with rotate. We can then tailor this particular aspect with transition properties. The shown example gives rotate a different duration time, and sets the animation to run forwards and backwards continuously.

Commit | Source code | Live example

One last thing we will do with the animation is add a bit of randomness into the rotation so the images don’t all rotate at the same time. We will create a couple of small helper functions for this, and add a delay and random duration.

import React from "react";
import { motion } from "framer-motion";

const TRANSITION_TIME_OPACITY_S = 1;
const TRANSITION_TIME_ROTATE_S = { MIN: 1.5, MAX: 2.5 };

function getRandomNumberBetween(min, max) {
  return Math.floor(Math.random() * max) + min;
}

function getRandomDelay(max_s) {
  return Math.random() * max_s;
}

function ImageHolder({ img, className }) {
  return (
      <motion.div
        key={img.src}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1, rotate: [-2, 0, 2] }}
        transition={{
          duration: TRANSITION_TIME_OPACITY_S,
          rotate: {
            yoyo: Infinity,
            duration: getRandomNumberBetween(
              TRANSITION_TIME_ROTATE_S.MIN,
              TRANSITION_TIME_ROTATE_S.MAX
            ),
            delay: getRandomDelay(TRANSITION_TIME_ROTATE_S),
          },
        }}
        className={className ? className : undefined}
      >
        <img src={img.src} alt={img.alt} />
      </motion.div>
  );
}

export default ImageHolder;


Commit | Source code | Live example

Cycle through the animals - useCycle

So, we have animated in our first scene. Now we need to cycle through the scene data we created to show each animal. Framer Motion provides a React hook, useCycle, for this very case.

We import it into our file, and initialize it as so, replacing the line const currentScene = hedgehogScene;.

  const [currentScene, setCurrentScene] = useCycle(
    hedgehogScene,
    raccoonScene,
    squirrelScene
  );

Pass all the objects you wish to cycle through to the hook, and it will give you back the currentScene, and a function to call when you want to progress to the next object (called setCurrentScene in this example).

This on it’s own doesn’t really do anything, as setCurrentScene has to be called to progress through the scenes. To do this, we will use the React hook useEffect to set a timeout that call setCurrentScene after a period of time:

  useEffect(() => {
    const timeOut = setTimeout(setCurrentScene, SLIDE_CHANGE_TIME_MS);
    return () => clearTimeout(timeOut);
  }, [currentScene, setCurrentScene]);

Every time currentScene is changed, a timeout is set to call setCurrentScene. We return a cleanup function for when the component is unloaded before the timeout has a chance to complete.

If we have a look at the banner now, we’ll see it fade in each scene with the parameters we chose. It is quite harsh, though, as the scene just disappears as the new one fades in, creating a jarring effect.


Commit | Source code | Live example

Exit animations - AnimatePresence

We’d like the previous scene to fade out as the new one fades in.  This is something that would be fairly complex to do if you were using solely CSS transitions/animations, as the previous scene’s DOM elements need to be kept around even after the data has changed.

Framer Motion has functionality to do this for you, making animating elements out of the DOM very straightforward.

To do this, import AnimatePresence from Framer Motion and wrap it around the element that we want to animate on exit. Add an exit state for the element and a unique key (so Framer can track the elements entering/exiting) as shown below:

import React from "react";
import { motion, AnimatePresence } from "framer-motion";

const TRANSITION_TIME_OPACITY_S = 1;
const TRANSITION_TIME_ROTATE_S = { MIN: 1.5, MAX: 2.5 };

function getRandomNumberBetween(min, max) {
  return Math.floor(Math.random() * max) + min;
}

function getRandomDelay(max_s) {
  return Math.random() * max_s;
}

function ImageHolder({ img, className }) {
  return (
    <AnimatePresence>
      <motion.div
        key={img.src}
        initial={{ opacity: 0 }}
        exit={{ opacity: 0 }}
        animate={{ opacity: 1, rotate: [-2, 0, 2] }}
        transition={{
          duration: TRANSITION_TIME_OPACITY_S,
          rotate: {
            yoyo: Infinity,
            duration: getRandomNumberBetween(
              TRANSITION_TIME_ROTATE_S.MIN,
              TRANSITION_TIME_ROTATE_S.MAX
            ),
            delay: getRandomDelay(TRANSITION_TIME_ROTATE_S),
          },
        }}
        className={className ? className : undefined}
      >
        <img src={img.src} alt={img.alt} />
      </motion.div>
    </AnimatePresence>
  );
}

export default ImageHolder;

It is as simple as that, now when the scene is switched to a new animal the images will fade out before being removed from the DOM. To show the effect of one fading out as another comes in its place, we have to use absolute positioning on the images to have them one above each other.


Commit | Source code | Live example

Icon and header - motion and variants

Now the images do what we want them to do, so the last thing for this post is to animate in and out the logo/title on the center of the page.

For this, we'll modify the IconHolder component to move the icon/text vertically as the scene changes.

import React from "react";
import { motion, AnimatePresence } from "framer-motion";

const ANIMATION_DURATION_S = 0.8;
function IconHolder({ icon, text }) {
  return (
    <div className="icon-holder">
      <AnimatePresence>
        <motion.div
          className="icon-holder__icon"
          key={text + "icon"}
          initial={{ y: "-100%", opacity: 0 }}
          exit={{ y: "-100%", opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ duration: ANIMATION_DURATION_S, ease: "easeInOut" }}
        >
          <img src={icon.src} alt={icon.alt} />
        </motion.div>
        <motion.h1
          className="icon-holder__text"
          key={text}
          initial={{ y: "100%", opacity: 0 }}
          exit={{ y: "100%", opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ duration: ANIMATION_DURATION_S, ease: "easeInOut" }}
        >
          {text}
        </motion.h1>
      </AnimatePresence>
    </div>
  );
}

export default IconHolder;

We again use <AnimatePresence> and an exit prop to allow both new and old scenes to be shown at the same time in order for their animations to finish. Another thing to note is that we moved away from motion.div for the header, and used motion.h1. The motion tag supports many HTML elements , allowing you to animate semantic elements you already have in your component's DOM.


Commit | Source code | Live example

You might notice that the code for the icon and text is very similar with a lot of duplication. We can use a feature called variants to remove some of the duplication, and abstract the animation properties away so they can be used by multiple components.

import React from "react";
import { motion, AnimatePresence } from "framer-motion";

const ANIMATION_DURATION_S = 0.8;

const getVariants = (direction) => ({
  initial: {
    y: direction === "top" ? "-100%" : "100%",
    opacity: 0,
    transition: { duration: ANIMATION_DURATION_S, ease: "easeInOut" },
  },
  animate: {
    y: 0,
    opacity: 1,
    transition: { duration: ANIMATION_DURATION_S, ease: "easeInOut" },
  },
});

function IconHolder({ icon, text }) {
  return (
    <div className="icon-holder">
      <AnimatePresence>
        <motion.div
          className="icon-holder__icon"
          key={text + "icon"}
          variants={getVariants("top")}
          initial={"initial"}
          exit={"initial"}
          animate={"animate"}
        >
          <img src={icon.src} alt={icon.alt} />
        </motion.div>
        <motion.h1
          className="icon-holder__text"
          key={text}
          variants={getVariants("bottom")}
          initial={"initial"}
          exit={"initial"}
          animate={"animate"}
        >
          {text}
        </motion.h1>
      </AnimatePresence>
    </div>
  );
}

export default IconHolder;

For this, we create a small function that returns a variants object based on if the element is to animate up and fade out, or down and fade out. We can see that the animation specifications are now shared between the two components and can be modified as one.


Commit | Source code | Live example

Recap and next steps

We've used Framer Motion to create an animated hero banner with multiple scenes that transition smoothly between each state. All this without having to write any animation or transition CSS, and without having to jump through hoops to keep an element in the DOM long enough to animate it when exiting.

Writing animations this way simplifies the process, you can define your animations while working in the React context, and can also share the same animations between different components.

This post has really just touched a small amount of what Framer Motion can do, you can create very complex animations and transitions with the library that are performant and easy to maintain.