Let's recreate the example from https://usehooks.com/useOnScreen/ but adding framer-motion for a parallax effect
The first thing will be installing the packages. For simplicity, I'm going to use the react-intersection-observer, that uses the IntersectionObserver API.
install
npm i framer-motion react-intersection-observer
The first look at our component will be 2 divs with a hight of 100vh each, and any background you like to make a differentiation.
export default function Home() {
return (
<div>
<div style={{ height: "100vh" }}>
<h1>Scroll down to next section π</h1>
</div>
<div style={{ height: "100vh", backgroundColor: "#23cebd" }}></div>
</div>
);
}
Let's add the div with an image that we'd like to see move when we scroll it into the viewport.
//* everything until here is the same *//
<div style={{ height: "100vh", backgroundColor: "#23cebd" }}>
<div style={{ marginLeft: "50px" }}>
<h1>Hey I'm on the screen</h1>
<img style={{ borderRadius: "30px" }}
alt="ralph from the simpsons waving his hand"
src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif"
/>
</div>
</div>
For the moment is a static image, we want it to appear when as a DOM element enters in the screen. For that we are going to use react-intersection-observer, let's update our code.
import { useInView } from "react-intersection-observer";
export default function Home() {
const [ref, isVisible] = useInView({ threshold: 0.7 });
return (
<div>
<div style={{ height: "100vh" }}>
<h1>Scroll down to next section π</h1>
</div>
<div ref={ref} style={{ height: "100vh", backgroundColor: "#23cebd" }}>
{isVisible && (
<div style={{ marginLeft: "50px" }}>
<h1>Hey I'm on the screen</h1>
<img
style={{ borderRadius: "30px" }}
alt="ralph from the simpsons waving his hand"
src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif"
/>
</div>
)}
</div>
</div>
);
}
the useInView will manage the communication with the IntersectionObserver API, we'll take from it the ref to be placed on the DOM element we want to observe and a boolean value for us to use. It will only take a threshold or a rootMargin, any of those will work and the propuse is to decide how much % of the DOM element we want to scroll before switching the isVisible boolean from false to true, in this case we wrote 0.7 that's equivalent to 70% of the DOM element has to be on screen to change our boolean.
Bringing framer-motion
For the moment our element is just appearing on the screen, that could be rude. Let's update our code to make it slide from the left.
import { useInView } from "react-intersection-observer";
import { motion } from "framer-motion";
export default function Home() {
const [ref, isVisible] = useInView({ threshold: 0.7 });
const variants = {
visible: {
opacity: 1,
x: 0,
},
hidden: {
opacity: 0,
x: -100,
},
};
return (
<div>
<div style={{ height: "100vh" }}>
<h1>Scroll down to next section π</h1>
</div>
<div style={{ height: "100vh", backgroundColor: "#23cebd" }}>
<motion.div
ref={ref}
variants={variants}
animate={isVisible ? "visible" : "hidden"}
transition={{ duration: 0.5, ease: "easeOut" }}
style={{ marginLeft: "50px" }}
>
<h1>Hey I'm on the screen</h1>
<img
style={{ borderRadius: "30px" }}
alt="ralph from the simpsons waving his hand"
src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif"
/>
</motion.div>
</div>
</div>
);
}
This would be the whole example, we did one important change besides adding the framer-motion code, changing the ref from the div with the 100vh to the div that wraps the text and the image we want to slide in.
Thank you for your time
My name is Jesus Cova, I'm a full-stack developer based in Quintana Roo, MΓ©xico.
Top comments (3)
Good tutorial. You can actually use Framer Motion's built in whileInView function instead of react-intersection-observer.
Nice Work incorporated this and works perfect.
Thanks
Donald Boulton
thank you very much for this article it's really helpfull