• Homeright arrow
  • Blogright arrow
  • How to Add Scroll-Based Motion Effects in Next.js Apps
Feature

How to Add Scroll-Based Motion Effects in Next.js Apps

One of the most effective ways to make webpages come to life is with scroll-triggered animations. With Motion.dev (the updated version of Framer Motion), you can animate objects as users scroll, whether you're creating a landing page, product showcase, or narrative layout.

I'll go over three key methods in this tutorial for using Motion to make scroll-based and scroll-triggered animations:

  • whileInView: for basic triggers like fading in/out
  • useInView: for grouped animations with Boolean logic
  • useScroll: for progress-based animations tied to scroll position

1. Fading an Element When It Enters the Viewport (whileInView)

Let’s start simple. Consider a scenario in which you want an element to fade out as it enters the viewport, such as a blue box that disappears to reveal content underneath. Making your div into a motion is the simplest way to accomplish this.div, establishing its starting opacity, and employing the whileInView prop to initiate a fresh opacity upon its scrolling into view. We'll also make sure it only runs once and adjust when it triggers.

Code:

import { motion } from "motion/react"
<motion.div
initial={{ opacity: 1 }}
whileInView={{ opacity: 0 }}
transition={{ duration: 1 }}
viewport={{ amount: 'all', once: true }}
className="h-64 w-64 bg-blue-500"
>
<p className="text-white text-xl">Content behind the blue box</p>
</motion.div>

What’s Happening:

  • initial sets the starting state (fully visible).
  • whileInView defines what changes when the element appears.
  • viewport.amount: 'all' means wait until the entire element is visible before animating.
  • once: true makes sure it only happens the first time — useful for storytelling or reveal effects.

Use this when you want something to fade, scale, or move when it enters the screen.

2. Trigger Multiple Animations Together Using useInView

Next, let's say you want to animate a group of boxes when a section enters the viewport. To accomplish this, set a reference to a wrapper element and use the useInView hook to determine when the container is visible on the screen. Once that has been identified, you can use the same Boolean state to start animations on any child elements.

Code:

import { useRef } from "react"
import { useInView, motion } from "motion/react"
const MultiAnimate = () => {
const ref = useRef(null)
const isInView = useInView(ref, { amount: 'all' })
return (
<div ref={ref} className="flex space-x-4 mt-20">
<motion.div animate={{ y: isInView ? -100 : 0 }} className="w-1/3 h-32 bg-red-500" />
<motion.div animate={{ y: isInView ? 100 : 0 }} className="w-1/3 h-32 bg-green-500" />
<motion.div animate={{ y: isInView ? -100 : 0 }} className="w-1/3 h-32 bg-blue-500" />
</div>
)
}

What’s Happening:

  • useRef() creates a reference to the section you want to observe.
  • useInView(ref) returns true or false depending on whether that section is visible.
  • Each box (motion.div) uses this Boolean to determine whether to animate.
  • This lets you trigger many animations with one scroll check.

Use this when you want to animate multiple items — cards, steps, icons — in sync.

3. Animate Based on Scroll Progress (useScroll)

More than just "in or out of view" is sometimes required; in some cases, you want to animate according to the user's scrolling distance. useScroll and useTransform are useful in this situation. The scroll progress (scrollYProgress) will be tracked, and the value (0 to 1) will be mapped into animations such as fading, scaling, and rotation.

3.1 Scroll-Based Progress Bar

To create a scroll indicator like a reading bar, use the scroll progress to scale a horizontal element.

const { scrollYProgress } = useScroll()
<motion.div
className="fixed top-0 left-0 h-1 bg-blue-600 origin-left"
style={{ scaleX: scrollYProgress }}
/>

What’s Happening:

  • scrollYProgress gives a number between 0 (top of page) and 1 (bottom).
  • scaleX uses that value to stretch the bar from 0% to 100%.
  • origin-left ensures it grows from left to right.

Perfect for blogs, articles, or long content.

3.2 Animate Background Color Based on Scroll

You can also transform scrollYProgress into a background color change.

const backgroundColor = useTransform(
scrollYProgress,
[0, 1],
["#ffffff", "#3b82f6"]
)

<motion.div
className="h-screen"
style={{ backgroundColor }}
/>

What’s Happening:

  • As you scroll, the color goes from white to blue.
  • useTransform maps the scroll progress into a CSS value.

Use this for section transitions or mood shifts on scroll.

3.3 Rotate Based on Element’s Scroll Visibility

To animate based on when a specific element is entering and exiting, set a target ref in useScroll and apply transformations to that element only.

const targetRef = useRef(null)
const { scrollYProgress } = useScroll({
target: targetRef,
offset: ["start end", "end start"]
})
const rotate = useTransform(scrollYProgress, [0, 1], [0, 180])


<motion.div
ref={targetRef}
style={{ rotate }}
className="h-64 w-64 bg-indigo-500 mx-auto"
></motion.div>

What’s Happening:

  • scrollYProgress now tracks this element, not the whole page.
  • Offset fine-tunes when the scroll starts and ends:
    1. "start end": Start rotating when the top of the element hits the bottom of the viewport.
    2. "end start": End when the bottom hits the top of the viewport.

Use this for rotating cards, spinning logos, or visual storytelling elements.

4. Fine-Tune Scroll Timings with offset

Use offset if you want exact control over when the animation occurs. Sayings like "start when the top hits the center" or "end when 25% has passed" are made possible by this.

Code:

const { scrollYProgress } = useScroll({
target: targetRef,
offset: ["start start", "end start"]
})

This setup:

  • Starts the animation when the top of the element meets the top of the screen.
  • Ends it when the bottom reaches the top — essentially, animating only when it’s fully visible at the top.

You can replace start, end with center, 25%, or even 200px.

5. Animate Inside a Horizontal Scroll Area

Sometimes your scroll container isn’t the page — it’s a horizontal slider or div. You can still use useScroll, but you’ll set both a container and a target to track progress.

Code:

const containerRef = useRef(null)
const targetRef = useRef(null)


const { scrollXProgress } = useScroll({
container: containerRef,
target: targetRef,
axis: "x",
offset: ["end start", "start start"]
})


<motion.div
ref={targetRef}
style={{ opacity: scrollXProgress }}
className="w-64 h-64 bg-purple-500"
></motion.div>

What’s Happening:

  • container: The horizontally scrollable div.
  • target: The child element you're tracking inside it.
  • As you scroll, the element fades out.

Use this for image sliders, horizontal timelines, or scroll-triggered reveals.

Final Thoughts

Regardless of whether you're making a one-page website or a complex user interface, scroll animations increase user engagement and guide the user's eye. Motion.dev makes it easy to use, expressive, and highly customizable.

To make your website smooth and visually appealing, enhancing the user experience, contact us. We will guide you on the journey.

footer-particlefooter-particlefooter-particlefooter-particlefooter-particle
back-to-top