Categories
React

Framer Motion — Correcting Distortions and Layout Animations

With the Framer Motion library, we can render animations in our React app easily.

In this article, we’ll take a look at how to get started with Framer Motion.

Scale Correction

Layout animations are performed with the transform property so we see smooth animations.

Animations may distort child elements.

We can fix this with the layout prop.

For example, we can write:

App.js

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

export default function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <motion.div
      layout
      data-isOpen={isOpen}
      initial={{ borderRadius: 50 }}
      className="parent"
      onClick={() => setIsOpen(!isOpen)}
    >
      <motion.div layout className="child" />
    </motion.div>
  );
}

styles.css

html,
body {
  min-height: 100vh;
  padding: 0;
  margin: 0;
}

* {
  box-sizing: border-box;
}

body {
  background: green;
  background-repeat: no-repeat;
  display: flex;
  justify-content: center;
  align-items: center;
}

.App {
  font-family: sans-serif;
  text-align: center;
}

.parent {
  background: white;
  width: 100px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.parent[data-isOpen="true"] {
  width: 200px;
  height: 200px;
}

.child {
  width: 40px;
  height: 40px;
  background: green;
  border-radius: 50%;
}

We animate the outer div by expanding it when we click on it.

We add the layout prop to the inner div so that we don’t get distorting when we animate the outer div.

Transforms can also distort boxShadow and borderRadius values.

To keep them constant, we can set the in the initial prop:

App.js

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

export default function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <motion.div
      layout
      data-isOpen={isOpen}
      initial={{ borderRadius: 50 }}
      className="parent"
      onClick={() => setIsOpen(!isOpen)}
    >
      <motion.div layout className="child" initial={{ borderRadius: "20%" }} />
    </motion.div>
  );
}

styles.css

html,
body {
  min-height: 100vh;
  padding: 0;
  margin: 0;
}

* {
  box-sizing: border-box;
}

body {
  background: green;
  background-repeat: no-repeat;
  display: flex;
  justify-content: center;
  align-items: center;
}

.App {
  font-family: sans-serif;
  text-align: center;
}

.parent {
  background: white;
  width: 100px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.parent[data-isOpen="true"] {
  width: 200px;
  height: 200px;
}

.child {
  width: 40px;
  height: 40px;
  background: green;
}

We set the initial prop of the child div.

And we remove the border-radius from styles.css .

Customizing Layout Animations

We can customize layout animations with the transition property.

For example, we can write:

App.js

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

export default function App() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <motion.div
      layout
      data-isOpen={isOpen}
      initial={{ borderRadius: 50 }}
      className="parent"
      onClick={() => setIsOpen(!isOpen)}
      transition={{
        layoutX: { duration: 0.3 },
        layoutY: { delay: 0.2, duration: 0.3 }
      }}
    >
      <motion.div layout className="child" />
    </motion.div>
  );
}

styles.css

html,
body {
  min-height: 100vh;
  padding: 0;
  margin: 0;
}
* {
  box-sizing: border-box;
}
body {
  background: green;
  background-repeat: no-repeat;
  display: flex;
  justify-content: center;
  align-items: center;
}
.App {
  font-family: sans-serif;
  text-align: center;
}
.parent {
  background: white;
  width: 100px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.parent[data-isOpen="true"] {
  width: 200px;
  height: 200px;
}
.child {
  width: 40px;
  height: 40px;
  background: green;
  border-radius: 50%;
}

We set the transition prop to add a delay when we animate the y direction.

We also set the duration of the animation in the y direction to 0.3 seconds.

And we set the animation in the x direction to 0.3 seconds.

Conclusion

We can correct distortions with layout animations.

And we can control how layout animations are rendered with Framer Motion.

Categories
React

Framer Motion — Animation Sequence and Layout Animations

With the Framer Motion library, we can render animations in our React app easily.

In this article, we’ll take a look at how to get started with Framer Motion.

Sequencing

The controls.start method returns a promise so we can use it to sequence animations.

For example, we can write:

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

export default function App() {
  const controls = useAnimation();

  const sequence = async () => {
    await controls.start({ x: 0 });
    return await controls.start({ opacity: 1 });
  };

  useEffect(() => {
    sequence();
  }, []);

  return (
    <motion.div
      animate={controls}
      style={{ backgroundColor: "red", width: 100, height: 100 }}
    />
  );
}

We have the sequence function that calls controls.start with different styles.

So this is what we’ll see when we load our component.

Dynamic Start

We can call controls.start dynamically.

For example, we can write:

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

export default function App() {
  const controls = useAnimation();

  useEffect(() => {
    controls.start((i) => ({
      opacity: 0,
      x: 100,
      transition: { delay: i * 0.3 }
    }));
  }, []);

  return (
    <ul>
      <motion.li custom={0} animate={controls}>
        foo
      </motion.li>
      <motion.li custom={1} animate={controls}>
        bar
      </motion.li>
      <motion.li custom={2} animate={controls}>
        baz
      </motion.li>
    </ul>
  );
}

We pass in a callback with the i parameter, which is the value that we passed into the custom prop.

And we pass into the animate prop to set the controls to control the animation.

Layout Animations

We can animate layouts with Framer Motion

For example, we can write:

App.js

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

const spring = {
  type: "spring",
  stiffness: 700,
  damping: 30
};

export default function App() {
  const [isOn, setIsOn] = useState(false);

  const toggleSwitch = () => setIsOn(!isOn);

  return (
    <div className="switch" data-isOn={isOn} onClick={toggleSwitch}>
      <motion.div className="handle" layout transition={spring} />
    </div>
  );
}

styles.css

html,
body {
  min-height: 100vh;
  padding: 0;
  margin: 0;
}

* {
  box-sizing: border-box;
}

.App {
  font-family: sans-serif;
  text-align: center;
}

.switch {
  width: 160px;
  height: 100px;
  background-color: green;
  display: flex;
  justify-content: flex-start;
  border-radius: 50px;
  padding: 10px;
  cursor: pointer;
}

.switch[data-isOn="true"] {
  justify-content: flex-end;
}

.handle {
  width: 80px;
  height: 80px;
  background-color: white;
  border-radius: 40px;
}

We add the spring animation effect and pass that into the transition prop.

The onClick prop has the function to control the isOn state.

The layout prop lets us animate the layout of the div.

We add the styles to style our div into a switch.

justify-content set to flex-start has the switch bottom on the left. and flex-end puts the switch button to the right.

Conclusion

We can control our animation progress with Framer Motion.

And we can apply animations for layouts.

Categories
React

Framer Motion — Dynamic Variants and Animation Controls

With the Framer Motion library, we can render animations in our React app easily.

In this article, we’ll take a look at how to get started with Framer Motion.

Dynamic Variants

We can animate each element differently with dynamic variants.

For example, we can write:

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

const variants = {
  visible: (i) => ({
    opacity: 1,
    transition: {
      delay: i * 0.3
    }
  }),
  hidden: { opacity: 0 }
};

const items = [
  {
    text: "foo",
    id: 1
  },
  {
    text: "bar",
    id: 2
  },
  {
    text: "baz",
    id: 3
  }
];

export default function App() {
  return (
    <div className="App">
      <motion.ul>
        {items.map(({ text, id }, i) => (
          <motion.li
            key={id}
            initial="hidden"
            custom={i}
            animate="visible"
            variants={variants}
          >
            {text}
          </motion.li>
        ))}
      </motion.ul>
    </div>
  );
}

to animate the li elements in App .

We set the delay of each item differently with the transition.delay property.

i is what we passed into the custom prop, and that’s what we get in the function.

variants has animation styles for different stages.

initial has the property name of the initial styles before animation.

And the animate prop has the property name of the final styles that are applied after the animation.

Component Animation Controls

We can control when animations start or stop.

To do this, we use the useAnimation hook to create the controls object, which lets us control when animation starts and ends.

Starting an Animation

We can use the controls.start method to start an animation.

For example, we can write:

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

export default function App() {
  const controls = useAnimation();

  useEffect(() => {
    const timer = setTimeout(() => {
      controls.start({
        x: "100%",
        backgroundColor: "green",
        transition: { duration: 3 }
      });
    }, 2000);
    return () => {
      clearTimeout(timer);
    };
  }, []);

  return (
    <motion.div
      animate={controls}
      style={{ backgroundColor: "yellow", width: 100, height: 100 }}
    />
  );
}

We call control.start with the styles applied after animation with the object.

It’s called within the setTimeout callback so that the animation is delayed.

We can also call controls.start with the variant name.

For example, we can write:

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

const variants = {
  visible: (i) => ({
    opacity: 1,
    transition: {
      delay: i * 0.3
    }
  }),
  hidden: { opacity: 0 }
};

export default function App() {
  const controls = useAnimation();

  useEffect(() => {
    const timer = setTimeout(() => {
      controls.start("hidden");
    }, 2000);
    return () => {
      clearTimeout(timer);
    };
  }, []);

  return (
    <motion.div
      animate={controls}
      variants={variants}
      style={{ backgroundColor: "yellow", width: 100, height: 100 }}
    />
  );
}

We set the variants to an object with styles so that we can pass in a property name into the control.start method.

Then we see the div goes from being displayed to being hidden.

Conclusion

We can apply styles dynamically with Framer Motion.

Also, we can control when our animation starts.

Categories
React

Framer Motion — Exit Animation and Staggering Animations

With the Framer Motion library, we can render animations in our React app easily.

In this article, we’ll take a look at how to get started with Framer Motion.

Exit Animations

We can add exit animation with Framer Motion.

For example, we can write:

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

const image = {
  src:
    "https://i.picsum.photos/id/23/200/300.jpg?hmac=NFze_vylqSEkX21kuRKSe8pp6Em-4ETfOE-oyLVCvJo"
};

export const Slideshow = ({ image }) => (
  <AnimatePresence>
    <motion.img
      key={image.src}
      src={image.src}
      initial={{ opacity: 0, y: 200 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  </AnimatePresence>
);

export default function App() {
  return (
    <>
      <Slideshow image={image} />
    </>
  );
}

We use the AnimatePresence component to enclose the motion.img component to show animation on the image when it’s being unloaded.

We set the inital and animate props to set the starting and ending styles respectively.

And we have the exit prop to set the style to show after animating.

Mount Animations

We can add animations when the component mounts when we set initial to false and we the animate prop.

For instance, we can write:

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

export default function App() {
  return (
    <>
      <motion.div
        style={{ backgroundColor: "red", width: 100, height: 100 }}
        animate={{ x: 100 }}
        initial={false}
      />
    </>
  );
}

to move the div 100px to the right when we mount the App component.

Propagation

If motion components have children, then changes in variants will be flown down through the component hierarchy.

For example, if we have:

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

export default function App() {
  const list = {
    visible: { opacity: 1 },
    hidden: { opacity: 0 }
  };

  const item = {
    visible: { opacity: 1, x: 0 },
    hidden: { opacity: 0, x: -100 }
  };

  return (
    <motion.ul initial="hidden" animate="visible" variants={list}>
      <motion.li variants={item}>foo</motion.li>
      <motion.li variants={item}>bar</motion.li>
      <motion.li variants={item}>baz</motion.li>
    </motion.ul>
  );
}

We animate the ul and li with the motion.ul components.

And we set the variants to set the styles for stages of the animation.

The initial prop has the initial style values of the animation.

And animate has the name of the style that’s applied at the end of the animation.

Orchestration

We can set delays in various parts of the animation.

For example, we can write:

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

export default function App() {
  const list = {
    visible: {
      opacity: 1,
      transition: {
        when: "beforeChildren",
        staggerChildren: 0.3
      }
    },
    hidden: {
      opacity: 0,
      transition: {
        when: "afterChildren"
      }
    }
  };

  const item = {
    visible: { opacity: 1, x: 0 },
    hidden: { opacity: 0, x: -100 }
  };

  return (
    <motion.ul initial="hidden" animate="visible" variants={list}>
      <motion.li variants={item}>foo</motion.li>
      <motion.li variants={item}>bar</motion.li>
      <motion.li variants={item}>baz</motion.li>
    </motion.ul>
  );
}

We add the list object to stagger the animation of the li elements with the staggerChildren property in the visible stage.

Then when property sets the stage when the staggering is applied.

Conclusion

We can add animation when our component unmounts and tweak our animation in various ways with Framer Motion.

Categories
React

Framer Motion — Drag and Scroll Progress

With the Framer Motion library, we can render animations in our React app easily.

In this article, we’ll take a look at how to get started with Framer Motion.

Drag

We can add drag and drop into our React app with Framer Motion.

For example, we can write:

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

export default function App() {
  return (
    <>
      <motion.div
        drag
        dragConstraints={{
          top: -50,
          left: -50,
          right: 50,
          bottom: 50
        }}
        style={{ backgroundColor: "red", width: 100, height: 100 }}
      />
    </>
  );
}

to make a draggable div.

We add the drag prop to make the div draggable in all directions.

And we add the dragConstraints prop to constrain the positions that the div can be dragged to.

MotionValues

We can use MotionValues to track the state and velocity of all animation values.

For example, we can write:

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

export default function App() {
  const x = useMotionValue(0);
  const background = useTransform(
    x,
    [-100, 0, 100],
    ["#f4fc03", "#ced41c", "#d4bb1c"]
  );

  return (
    <motion.div style={{ background }}>
      <motion.div
        drag="x"
        dragConstraints={{ left: 0, right: 0 }}
        style={{ x }}
      >
        <div x={x}>foo</div>
      </motion.div>
    </motion.div>
  );
}

to show a different background when we drag the ‘foo’ text across the screen.

We do this by using the useTransform hook with the useMotionValue hook to create the value to track.

useTransform takes the value to track as the first argument.

The 2nd argument has the position to drag to.

The 3rd argument has the color for that’s displayed when we drag the ‘foo’ text.

The x MotionValue is applied by setting the x property of the style prop of the div .

We do the same with the div.

And we add the drag and dragConstraints to enable dragging and set the location that we can drag to.

We set left and right to 0, so we bounce the ‘foo’ text back to its original position.

We set the background property in the style prop of motion.div to set the background.

Viewport Scroll

We can watch for viewport scroll progress and animate content accordingly.

For example, we can write:

import React, { useEffect, useState } from "react";
import {
  motion,
  useSpring,
  useTransform,
  useViewportScroll
} from "framer-motion";

export default function App() {
  const [isComplete, setIsComplete] = useState(false);
  const { scrollYProgress } = useViewportScroll();
  const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1]);
  const pathLength = useSpring(yRange, { stiffness: 400, damping: 90 });

  useEffect(() => yRange.onChange((v) => setIsComplete(v >= 1)), [yRange]);

  return (
    <>
      <svg
        className="progress-icon"
        viewBox="0 0 200 200"
        style={{ position: "fixed", top: 0, left: 50 }}
      >
        <motion.path
          fill="none"
          strokeWidth="5"
          stroke="green"
          strokeDasharray="0 1"
          d="M 0, 20 a 20, 20 0 1,0 40,0 a 20, 20 0 1,0 -40,0"
          style={{
            pathLength,
            rotate: 90,
            translateX: 5,
            translateY: 5,
            scaleX: -1
          }}
        />
        <motion.path
          fill="none"
          strokeWidth="5"
          stroke="green"
          d="M14,26 L 22,33 L 35,16"
          initial={false}
          strokeDasharray="0 1"
          animate={{ pathLength: isComplete ? 1 : 0 }}
        />
      </svg>
      <div>
        {Array(100)
          .fill()
          .map((_, i) => (
            <p key={i}>{i}</p>
          ))}
      </div>
    </>
  );
}

We add the svg with a circle and a checkmark.

The pathLength will increase as we scroll down.

This is because we used the useViewportScroll hook to watch the scrollYProgress .

Then we use the useTransform hook to watch that and create the yRange object.

We call setIsComplete when the yRange value changes so so that we only show the checkmark when we scroll to the bottom.

The first motion.path has the circle. We increase the pathLength as we scroll down to show more of the circle as we scroll down.

The 2nd motion.path has the checkmark, we only show this when isComplete is true .

We scroll all the way down to make isComplete true .

Conclusion

We can add drag and drop and track scroll progress with Framer Motion.