Framer Motion

February 11, 2020 (4y ago)

Archive

I have a question for you. How would you make this react element, fade in on view?

1const FadingText: React.FC = () => {
2  return (
3    <h1
4      className=" text-4xl font-bold
5     bg-gradient-to-r from-teal-500 to-indigo-500 
6     text-transparent bg-clip-text "
7    >
8      I&apos;m Fading in
9    </h1>
10  );
11};
12

The Traditional Approach

You'd create CSS styles for fading, handle state changes, and use the IntersectionObserver API. It's effective but requires several steps and can get a bit verbose.

First, Create the fade in style

1.fade-in {
2  opacity: 0;
3}
4.fade-in.visible {
5  opacity: 1;
6}
7

Then wrap the element in a parent that checks its visibility state and fades it in if it's visible.

1const Component: React.FC = () => {
2
3  const [isVisible, setIsVisible] = useState(false);
4
5  return (
6    <div className="flex items-center justify-center py-2">
7      <div
8        className={`transition-opacity fade-in ${isVisible ? 'visible' : ''}`}
9      >
10        <FadingText />
11      </div>
12    </div>
13  );
14};
15

To trigger the state change when the element mounts, useEffect is needed with a reference to the target element:

1const Component: React.FC = () => {
2
3  const [isVisible, setIsVisible] = useState(false);
4  const ref = useRef<HTMLDivElement>(null);
5
6  useEffect(() => {
7    // Handle it when in view
8    setIsVisible(!isVisible);
9  }, [ref]);
10
11  return (
12    <div className="flex items-center justify-center py-2">
13      <div
14        ref={ref}
15        className={`transition-opacity fade-in ${isVisible ? 'visible' : ''}`}
16      >
17        <FadingText />
18      </div>
19    </div>
20  );
21};
22

But you don't need the state to change when the element mounts, instead you want it to fade in when it comes in view, to make it happen I'll be using the IntersectionObserver API which basically detects when an element comes into or goes out of view within the browser's viewport

1const observer = new IntersectionObserver(
2      ([entry]) => {
3        if (entry.isIntersecting) {
4            // make the element invisible;
5          observer.unobserve(entry.target);
6        }
7      },
8      { threshold: 0.5 } /* trigger when at least 50% of the observed
9       element is visible in the viewport. */
10    );
11

With this setup, the component is ready to gracefully fade in.

1
2import React, { useEffect, useRef, useState } from 'react';
3import './styles.css';
4
5const Component: React.FC = () => {
6  const [isVisible, setIsVisible] = useState(false);
7  const ref = useRef<HTMLDivElement>(null);
8
9  useEffect(() => {
10    const current = ref.current;
11    const observer = new IntersectionObserver(
12      ([entry]) => {
13        if (entry.isIntersecting) {
14          setIsVisible(!isVisible);
15          observer.unobserve(entry.target);
16        }
17      },
18      { threshold: 0.5 }
19    );
20    if (current) {
21      observer.observe(current);
22    }
23    return () => {
24      if (current) {
25        observer.unobserve(current);
26      }
27    };
28  }, [ref]);
29
30  return (
31    <div className="flex items-center justify-center py-2">
32      <div
33        ref={ref}
34        className={`transition-opacity fade-in ${isVisible ? 'visible' : ''}`}
35      >
36        <FadingText />
37      </div>
38    </div>
39  );
40};
41
42export default Component;
43const FadingText: React.FC = () => {
44  return (
45    <h1 className=" text-4xl font-bold bg-gradient-to-r from-teal-500 to-indigo-500 text-transparent bg-clip-text ">
46      I&apos;m Fading in
47    </h1>
48  );
49};
50

Resulting in

I'm Fading in

A bit cumbersome for a subtle fade-in effect. Look how much of work that took. Now I'll be doing the same except this time with Framer Motion

Using Framer

1
2import React from 'react';
3import { motion } from 'framer-motion';
4
5const FramerMotionFadeInComponent: React.FC = () => {
6  return (
7    <div className="flex justify-center items-center py-2">
8      <FadingText />
9    </div>
10  );
11};
12

Now all I have to do is to turn any HTML element into a motion element, in this case it's on the component's surrounding div. And then just add some props provided by framer.

1
2import React from 'react';
3import { motion } from 'framer-motion';
4
5const FramerMotionFadeInComponent: React.FC = () => {
6  return (
7     <div className="flex justify-center items-center py-2">
8      <motion.div
9        viewport={{ once: true }} // only run once per load
10        whileInView={{
11          opacity: 1,
12        }}
13        initial={{
14          opacity: 0,
15        }}
16        transition={{
17          duration: 1,
18          ease: 'easeInOut',
19        }}
20      >
21        <FadingText />
22      </motion.div>
23      {// the rest of the page}
24    </div>
25  );
26};
27

Clean, easy and efficient.

Yeet Me

Creating this YeetMe component would have been really hard with plain CSS & JS

Here's the code for it

1import { motion } from 'framer-motion';
2
3export default function YeetMe() {
4  const initial = {
5    opacity: 0,
6    borderRadius: 0,
7    scale: 0,
8    rotate: 360,
9  };
10
11  const animate = {
12    opacity: 1,
13    width: 160,
14    height: 70,
15    borderRadius: 20,
16    scale: 1,
17    boxShadow: '10px 10px 0 rgba(255, 46, 199, 0.2)',
18    rotate: 0,
19  };
20
21  const transition = {
22    duration: 1,
23    type: 'keyframes',
24    ease: 'easeInOut',
25  };
26
27  const whileHover = {
28    cursor: 'grab',
29  };
30
31  const whileDrag = {
32    cursor: 'grabbing',
33  };
34  
35  return (
36    <motion.button
37      layout
38      viewport={{ once: true }}
39      className="flex justify-center items-center origin-center bg-gradient-to-r from-purple-500 via-indigo-500 to-pink-500 w-80 h-80"
40      initial={initial}
41      whileInView={animate}
42      transition={transition}
43      whileHover={whileHover}
44      drag
45      whileDrag={whileDrag}
46      dragConstraints={{
47        top: -10,
48        left: -10,
49        right: 10,
50        bottom: 10,
51      }}
52      dragMomentum={true}
53      dragPropagation
54    >
55      <motion.div
56        layout
57        viewport={{ once: true }}
58        className="flex justify-center items-center origin-center bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 w-80 h-80"
59        initial={initial}
60        whileInView={animate}
61        transition={transition}
62        whileHover={whileHover}
63        whileDrag={whileDrag}
64        drag
65      >
66        <span className=" font-bold">yeet me</span>
67      </motion.div>
68    </motion.button>
69  );
70}
71

These are just a couple of examples, the library offers more features and options for creating sophisticated animations. You can check out the docs for more info.