Using Intersection Observer and Framer Motion for Scroll-Based Animations in React

Brad Carter
3 min readFeb 1, 2021
Simple fade-in when a component enters the viewport via horizontal scroll.

Framer Motion is such an intuitive and easy-to-use animation library that I’m honestly a little surprised it doesn’t have built-in tracking for whether a motion component is in the viewport. It can tell us whether a component is present in the React tree or how far we’ve scrolled through an element/viewport, but none of those are really ideal for triggering animations at the exact moment an element enters the viewport. Luckily this functionality is pretty easy to implement using react-intersection-observer. I’ll also assume you already have a project set up and won’t get into horizontal scrolling but here’s the tutorial I followed.

Start by importing { motion } from framer-motion and the { InView } component from react-intersection-observer (there’s also a hook version useInView but because I’m tracking multiple elements I found the component approach to be simpler). Turn the first component you’d like to animate into a motion component and define its initial (hidden) state and animate (value you’d like to animate to) state, along with any other props you’d like to include.

Currently this will animate as soon as it mounts (aka before it’s in the viewport).

Now, in order to track this component, we’ll wrap it inside an InView component and pass the ref and inView props to the wrapped motion component. The ref is to target the component we’re animating, and inView will return a boolean we can use to trigger the animation. We can also give the InView component a threshold (number between 0 and 1) to tell it how much of the element should be in the viewport before inView returns true (in the example below it would be 25%).

The motion.img component is now animating to full opacity when inView is true, and back to zero opacity when inView returns false again. But what if we wanted it to keep its opacity so that it doesn’t fade away after scrolling past it? Luckily this is pretty simple to achieve — just add triggerOnce to your InView component (you could also just pass null as the “false” value for animate but that seems a little messier). We could also repeat this same approach for every page element we want to animate on scroll. Beyond that, have fun experimenting with your animate/transition values and thanks for reading!