Adding Animations to React with framer-motion

04/01/2024

Intro about framer-motion

In order to make this site "pop" more, I decided to add some animations. I wanted a robust and reusable system that is easy to maintain and implement, so I checked out available packages to see which one will suit my needs the best. After some research, I came upon framer motion.

Framer motion is an open-source animation library built for React on framer. It features many different types of animations with a very literate way of implementing them.

Installation and a simple example

To install the package, in your react project in a console use the command npm i framer-motion. Then to use it in any component, just import motion from the package which is an object that can extend to pretty much any html element. This is an example of a simple react component using state and hiding/showing a div:

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

const simpleExample = () => {
  const [vis, setVis] = useState(true);
  return (
    <>
      <div onClick={() => setVis(!vis)}>Toggle div opacity</div>
      <motion.div animate={{ opacity: vis ? 1 : 0 }}>It's working!</motion.div>
    </>
  );
};

export default simpleExample;

We used the motion object to show a div, but we bould have used it with pretty much all html elements (input, button, form, etc.).

Organising animation variants

To take this to the next level and also organize your animations and make them more complex. I didn't go too overboard with mine, most of them just fade in and appear from a side. In the next snippet I am sharing all the animations used on this blog, with comments explaining what they are doing. I have them in a file called AnimationVariants.tsx in my common/utils folder, but you can place them anywhere you like to match your architecture. The viewport: { once: true } parameter makes sure that the animations are only displayed once when the element comes in the viewport of the browser.

export const snapFromRightAnimation = {
  initial: "hidden",
  whileInView: "visible",
  viewport: { once: true },
  variants: {
    visible: { x: 0, opacity: 1 },
    hidden: { x: 50, opacity: 0 },
  },
};

export const snapFromLeftAnimation = {
  initial: "hidden",
  whileInView: "visible",
  viewport: { once: true },
  variants: {
    visible: { x: 0, opacity: 1 },
    hidden: { x: -50, opacity: 0 },
  },
};

export const snapFromTopAnimation = {
  initial: "hidden",
  whileInView: "visible",
  viewport: { once: true },
  variants: {
    visible: { y: 0, opacity: 1 },
    hidden: { y: -50, opacity: 0 },
  },
};

export const snapFromBottomAnimation = {
  initial: "hidden",
  whileInView: "visible",
  viewport: { once: true },
  variants: {
    visible: { y: 0, opacity: 1 },
    hidden: { y: 50, opacity: 0 },
  },
};

export const snapFromBottomAnimationSlow = {
  initial: "hidden",
  whileInView: "visible",
  viewport: { once: true },
  variants: {
    visible: { y: 0, opacity: 1 },
    hidden: { y: 50, opacity: 0 },
  },
};

Animation delays

When there are multiple elements to be displayed on a page, usually visible at the same time, them animating all at once is usually not very aesthetically pleasing. So to countereffect that, I have added animation delays. In the same file, I have added the following:

function generateRandomInteger(max: number) {
  return Math.floor(Math.random() * max) + 1;
}

// randomShortInterval is used when there is too many animations going on in a screen so we just want to shuffle it up
export const randomShortInterval = () => generateRandomInteger(30) / 100;

// animationDelay is a constant used to have different timeframes to be used as delays for animations, for example in the header menu of this website where I wanted to show the menu items one by one
export const animationDelay = {
  1: 0.15,
  2: 0.3,
  2.1: 0.325,
  2.2: 0.35,
  2.3: 0.375,
  2.4: 0.4,
  3: 0.45,
  4: 0.6,
  5: 0.75,
};

Then in my Header.tsx file which I use for the main menu in this app (you can view the full code for it here), I have used the animationDelay props to cause the menu items to appear one after another, creating a smoother animation.

import {
  animationDelay,
  snapFromTopAnimation
} from "../utils/AnimationVariants";

<ul>
    <motion.li
      {...snapFromTopAnimation}
      transition={{
        delay: animationDelay[2],
      }}>	
      <Link to={"/"}>
        Tech Stack
      </Link>
    </motion.li>
    <motion.li
      {...snapFromTopAnimation}
      transition={{
        delay: animationDelay[2.1],
      }}>	
      <Link to={"/blog"}>
        Blog
      </Link>
    </motion.li>
    ...
</ul>

For further research and assistance, you can check the framer motion documentation which has many more examples and even better explanations.

Share this post :