Published on

Colorful Framer-Motion animation example in React

Authors
  • avatar
    Name
    Alessandro Battiato
    Twitter

I want to show you something very cool 👀

import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import './styles.css';

const Circle = ({
  circleColor,
}) => {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const circleSize = Math.max(windowSize.width, windowSize.height);
  const outerCircleTransition = { duration: 1.2 };
  const innerCircleTransition = { duration: 1.125 };
  const circleClass = 'fixed inset-0 m-auto rounded-full z-30';
  const safeGrowthScale = 1.5;
  const SafeShrinkScaleInnerCircle = 4;

  return (
    <>
      <motion.div
        style={{
          width: circleSize,
          height: circleSize,
          left: -(circleSize - windowSize.width) / 2,
        }}
        className={`${circleClass} bg-${circleColor}`}
        initial={{ scale: safeGrowthScale + 1 }}
        animate={{ scale: 0 }}
        transition={outerCircleTransition}
      />
      <motion.div
        style={{
          width: circleSize / SafeShrinkScaleInnerCircle,
          height: circleSize / SafeShrinkScaleInnerCircle,
        }}
        className={`${circleClass} bg-black`}
        initial={{ scale: safeGrowthScale * SafeShrinkScaleInnerCircle }}
        animate={{ scale: 0 }}
        transition={innerCircleTransition}
      />
    </>
  );
};

const App = () => {
  return (
    <Circle
      circleColor="blue"
    />
  );
};

export default App;

It's amazing, isn't it? And this is only a taste of the amazing things you can create with Framer Motion!

Framer Motion, the Maestro of your Orchestra!

Framer Motion is like the choreographer for your designs: it lets you add elegant animations, smooth transitions, and interactive responses, thanks to the properties that the motion component offers.

The animation you just saw can be used as a splash screen to make colorful and smooth transitions between the pages of your website, so we're going to build this together step-by-step!

Project pre-requisites

You'll need basic knowledge of the core front-end technologies (such as HTML5, CSS3 and JavaScript) and also basic knowledge of React's functional components.

Don't worry if you're still not familiar with framer motion, we'll cover the basics right away (if you're already familiar with framer motion then you can skip the following section!)

Motion, the core block of framer-motion ✨

Think of the motion component as a power-up for your elements!

If you wanted to render a div element using framer motion, you would see something like this:

<motion.div>I'm a super div 💪</motion.div>

It looks like there is nothing special about this right? Well, the magic of framer-motion is all about the properties that you can now use on your div!

For example, your motion.div now supports the initial property, which lets you set an initial property (or set of properties) that your div should follow when rendered, then you can implement an animation using the animate property and control which properties should change, while the transition property has the responsibility of dictating how your animation should change.

For instance, if we wanted to render a motion.div using these 3 properties in order to implement a simple animation, we should follow these steps:

  • Set scale to 1 for the "initial" prop as the initial size of our motion.div

  • Double the scale value, rotate by 180 degrees and apply a border radius of 50% in the animate property which will be the parameters applied during the animation

  • Finally customize the animation by setting 1 as duration value (which equals 1 second) and set the animation type to easeinout

Here's an example showcasing what we've just discussed:

import "./styles.css";
import { motion } from "framer-motion";

export default function App() {
  return (
    <motion.div
      className="box"
      initial={{ scale: 1 }}
      animate={{
        scale: 2,
        rotate: 180,
        borderRadius: "50%"
      }}
      transition={{
        duration: 1,
        ease: "easeInOut",
      }}
    />
  );
}

This was just a basic example to show off the core properties we are going to use for our little project!

Creating the inner Circle

First we need to create the inner circle, then we are going to implement the animation, so at first it'll feel as if a black hole has engulfed us!

import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import './styles.css';

const Circle = () => {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const circleSize = Math.max(windowSize.width, windowSize.height);
  const circleClass = 'fixed inset-0 m-auto rounded-full z-30';
  const safeGrowthScale = 1.5;

  return (
    <motion.div
        style={{
            width: circleSize,
            height: circleSize
        }}
        className={`${circleClass} bg-black`}
        initial={{ scale: safeGrowthScale }}
    />
  );
};

const App = () => {
  return (
    <Circle />
  );
};

export default App;

We use the useEffect hook in order to get the current device's sizes such as the height and width, then we use Math.max in order to use the higher value between the two of them, but due to the shape of the full circle (obtained by applying the rounded-full class) the corners of the screen won't be covered as well, thus why we are using the motion.div's initial prop as to scale the size of the circle even more using the safeGrowthScale, I suggest you not to increase the latter too much as making the circle too big will make our final animation feel sluggish.

It's time for the motion.div's animate prop to kick in!

import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import './styles.css';

const Circle = () => {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const circleSize = Math.max(windowSize.width, windowSize.height);
  const innerCircleTransition = { duration: 1.125 };
  const circleClass = 'fixed inset-0 m-auto rounded-full z-30';
  const safeGrowthScale = 1.5;

  return (
    <motion.div
        style={{
            width: circleSize,
            height: circleSize
        }}
        className={`${circleClass} bg-black`}
        initial={{ scale: safeGrowthScale }}
        animate={{
            scale: 0,
        }}
        transition={innerCircleTransition}
    />
  );
};

const App = () => {
  return (
    <Circle />
  );
};

export default App;

We set the size of the circle to 0 thanks to the animate prop, and we also create a new variable innerCircleTransition, to control the duration of the animation. It's time to wrap up and implement our outer Circle!

Creating the outer Circle

As you can see in the first code playground, our outer Circle is way bigger than the inner one, and even when the animation is about to end, we can see that the inner Circle "collapses" inside the outer one, so we have a few things to keep in mind.

  • We have to scale the outer Circle more than we already do with the inner Circle
  • We have to make the outer Circle's animation last slightly more
  • We have to give some hue to the outer Circle!
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import './styles.css';

const Circle = ({
  circleColor,
}) => {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const circleSize = Math.max(windowSize.width, windowSize.height);
  const outerCircleTransition = { duration: 1.2 };
  const innerCircleTransition = { duration: 1.125 };
  const circleClass = 'fixed inset-0 m-auto rounded-full z-30';
  const safeGrowthScale = 1.5;
  const SafeShrinkScaleInnerCircle = 4;

  return (
    <>
      <motion.div
        style={{
          width: circleSize,
          height: circleSize,
          left: -(circleSize - windowSize.width) / 2,
        }}
        className={`${circleClass} bg-${circleColor}`}
        initial={{ scale: safeGrowthScale + 1 }}
        animate={{ scale: 0 }}
        transition={outerCircleTransition}
      />
      <motion.div
        style={{
          width: circleSize / SafeShrinkScaleInnerCircle,
          height: circleSize / SafeShrinkScaleInnerCircle,
        }}
        className={`${circleClass} bg-black`}
        initial={{ scale: safeGrowthScale * SafeShrinkScaleInnerCircle }}
        animate={{ scale: 0 }}
        transition={innerCircleTransition}
      />
    </>
  );
};

const App = () => {
  return (
    <Circle
      circleColor="red"
    />
  );
};

export default App;

We added a couple of color classes as to give more freedom over the outer Circle's color and thus the Circle component now accepts a circleColor prop, we also provided safeGrowthScale + 1 as scale value for the outer Circle to be big enough to contain the inner Circle, and more importantly we created outerCircleTransition in order to make the outer Circle's animation last more, and that's how we get to this cool double black-hole collapsing effect!

Conclusion

As stated earlier, you can use this animation for page transitions, but it would surely be more complex.

When I used it in a Next.js project, I had to introduce variables to keep track of the loading state for the content of the pages, but overall this was just a small project to highlight the strength of framer-motion and to show how easy it is to implement complex yet powerful and beautiful animations thanks to the tools that this library offers, and we haven't even talked about the AnimatePresence component yet, but we surely will in the future!

Until next time :)