Categories
React

Framer Motion — Handling Gestures

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.

Gestures

Framer Motion is capable of recognizing gestures.

For example, we can write:

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

export default function App() {
  return (
    <motion.button
      whileHover={{
        scale: 1.2,
        transition: { duration: 1 }
      }}
      whileTap={{ scale: 0.9 }}
    >
      hello world
    </motion.button>
  );
}

to change the size of the button when we hover or click on it.

scale changes the size. transition.duration sets the duration of the effect.

We can also set the variants prop to set the effects for the button.

For example, we can write:

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

const variants = {
  buttonVariants: { opacity: 1 },
  iconVariants: { opacity: 0.5 }
};

export default function App() {
  return (
    <motion.button whileTap="tap" whileHover="hover" variants={variants}>
      <svg>
        <motion.path variants={variants} />
      </svg>
    </motion.button>
  );
}

We set the whileTap and whileHover to set the handlers for these gestures.

And we set the variants prop to set the effect that we want to see.

Hover

We can add animation when we hover over an element with the whileHover prop.

For instance, we can write:

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

export default function App() {
  return (
    <motion.div
      whileHover={{ scale: 1.2 }}
      onHoverStart={(e) => {
        console.log(e);
      }}
      onHoverEnd={(e) => {
        console.log(e);
      }}
    >
      hello
    </motion.div>
  );
}

Then we can listen to hover start and hover end events with the onHoverStart and onHoverEnd props.

e has the object that we can get information about the hovering.

Tap

We can also handle animation with the tap event with the whileTap prop.

For example, we can write:

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

export default function App() {
  return <motion.div whileTap={{ scale: 0.8 }}>hello</motion.div>;
}

We set the animation effect we want to apply with the whileTap prop.

And we can listen for taps with the onTap prop:

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

function onTap(event, info) {
  console.log(info.point.x, info.point.y);
}

export default function App() {
  return (
    <motion.div onTap={onTap} whileTap={{ scale: 0.8 }}>
      hello
    </motion.div>
  );
}

We get the position of the tap with the onTap function.

info.point.x and info.point.y have the coordinates of the tap.

Pan

We can also listen for panning events with Framer Motion.

For instance, we can write:

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

function onPan(event, info) {
  console.log(info.point.x, info.point.y);
}

export default function App() {
  return <motion.div onPan={onPan}>hello</motion.div>;
}

to listen for pans.

When we drag the div, onPan will be run.

Framer Motion components also take the onPanStart and onPanEnd props to listen for start and end of panning.

For example, we can write:

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

function onPan(event, info) {
  console.log(info.point.x, info.point.y);
}

function onPanStart(event, info) {
  console.log(info.point.x, info.point.y);
}

function onPanEnd(event, info) {
  console.log(info.point.x, info.point.y);
}

export default function App() {
  return (
    <motion.div onPan={onPan} onPanStart={onPanStart} onPanEnd={onPanEnd}>
      hello
    </motion.div>
  );
}

Conclusion

We can listen for various gestures with the Framer Motion library.

Categories
React

Framer Motion — Shared 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.

Shared Layout Animations

We can create shared layout animations with the AnimateSharedLayout component.

For example, we can write:

App.js

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

function Item({ text }) {
  const [isOpen, setIsOpen] = useState(false);

const toggleOpen = () => setIsOpen(!isOpen);

return (
    <motion.li layout onClick={toggleOpen} initial={{ borderRadius: 10 }}>
      <motion.div layout>{text}</motion.div>
      <AnimatePresence>{isOpen && <Content />}</AnimatePresence>
    </motion.li>
  );
}

function Content() {
  return (
    <motion.div
      layout
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    >
      <div className="row" />
      <div className="row" />
      <div className="row" />
    </motion.div>
  );
}

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

export default function App() {
  return (
    <AnimateSharedLayout>
      <motion.ul layout>
        {items.map(({ text, id }) => (
          <Item text={text} key={id} />
        ))}
      </motion.ul>
    </AnimateSharedLayout>
  );
}

styles.css

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

* {
  box-sizing: border-box;
}

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

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

ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}

ul {
  width: 300px;
  display: flex;
  flex-direction: column;
  background: white;
  padding: 20px;
  border-radius: 25px;
}

li {
  background-color: rgba(214, 214, 214, 0.5);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 20px;
  overflow: hidden;
  cursor: pointer;
}

li:last-child {
  margin-bottom: 0px;
}

.avatar {
  width: 40px;
  height: 40px;
  background-color: #666;
  border-radius: 20px;
}

.row {
  width: 100%;
  height: 8px;
  background-color: #999;
  border-radius: 10px;
  margin-top: 12px;
}

We have the Item component that lets us toggle the content of the div.

This is done with the AnimatPresence component which shows the Content component’s content if it’s true .

The row class creates the bars that we want to show.

In App , we have the AnimateSharedLayout component wrapped around our ul to let us show the same animation for each li .

The animation is synced across a set of components that don’t share state.

And they let us perform animation between different components with a common layoutId as they’re added or removed.

When a component with the 1ayoutId prop is removed from one part of the tree and it’s added elsewhere, the new component will automatically animate from the old component’s position.

For example, we can write:

App.js

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

export default function App() {
  const [selected, setSelected] = useState(colors[0]);

return (
    <AnimateSharedLayout>
      <ul>
        {colors.map((color) => (
          <Item
            key={color}
            color={color}
            isSelected={selected === color}
            onClick={() => setSelected(color)}
          />
        ))}
      </ul>
    </AnimateSharedLayout>
  );
}

function Item({ color, isSelected, onClick }) {
  return (
    <li className="item" onClick={onClick} style={{ backgroundColor: color }}>
      {isSelected && (
        <motion.div
          layoutId="outline"
          className="outline"
          initial={false}
          animate={{ borderColor: color }}
          transition={spring}
        />
      )}
    </li>
  );
}

const colors = ["red", "green", "blue", "yellow"];

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

styles.css

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

* {
  box-sizing: border-box;
}

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

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

ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  width: 280px;
  height: 380px;
}

.item {
  width: 100px;
  height: 50px;
  margin: 20px;
  position: relative;
  cursor: pointer;
  flex-shrink: 0;
}

.outline {
  position: absolute;
  top: -20px;
  left: -20px;
  right: -20px;
  bottom: -20px;
  border: 10px solid white;
}

We add a frame around the li that’s clicked on by toggling the isSelected state in the Item component.

In styles.css , we have the outline class to define the outline.

That is toggled when we toggle the li .

In the App component, we select the item that we clicked on with the setSelected function.

Conclusion

We can add shared layouts animation to perform animations synced across a set of components that don’t share states.

And we can also perform animations between different components with a common layoutId as they’re added or removed.

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.