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'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'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.