Scroll-Reveal/Hide Animation with Intersection Observer in Next.js

thumbnail

Hello Guys In this tutorial, we’ll unravel the secrets behind creating captivating on-scroll effects that seamlessly elevate the user experience. One popular technique is the scroll-reveal animation, where elements come into view as the user scrolls down the page, today we’ll explore how to implement such animations using Intersection Observer in a Next.js application.

Step 1: Setting Up the Project

Begin by setting up your Next.js project.

npx create-next-app@latest

Step 2: Creating the Reveal Component

In src/components let’s create the RevealComp component encapsulates the logic for triggering animations when elements enter/exit the viewport. Let's break down its key features:

Props:

  • children: The content to be revealed.
  • threshold: The percentage of the element that must be visible to trigger the animation.
  • duration: The duration of the animation.
  • x and y: Optional parameters for horizontal and vertical translation.

Code:

// src/components/reveal.tsx
import React, { useEffect, useRef, useState } from 'react';
const RevealComp = ({ children, threshold, duration, x, y }) => {
    x = x || 0
    y = y || 0
    const ref = useRef(null)
    const [intersecting, setIntersecting] = useState(false)
};
export default RevealComp;
  • x and y are set to default values of 0 if not provided.
  • ref is initialized using useRef to reference the DOM element.
  • intersecting state is set to false initially using useState to track whether the element is intersecting the viewport.

Code:

...
useEffect(() => {
    if (ref.current) {
        const intersectionObserver = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) {
                setIntersecting(true)
            } else {
                setIntersecting(false)
            }
        }, {
            threshold
        })

        intersectionObserver.observe(ref.current)

        return () => {
            if (ref.current) {
                intersectionObserver.unobserve(ref.current)
            }
        }
    }
}, [])
...
  • useEffect is used to set up and clean up the Intersection Observer.
  • It observes the ref element and updates the intersecting state based on whether the observed element is intersecting with the viewport.
  • The threshold prop is used to specify the percentage of the target element's visibility needed to trigger the intersection.
  • Try printing the entries variable to see the values that observer provides.

Code:

...
return (
    <div
        style={
            {
                transitionDuration: duration,
                transform:!intersecting ? `translate(${x}px, ${y}px)` : "translate(0px, 0px)"
            }
        }
        className={`transition ${intersecting ? "opacity-100" : "opacity-0"}`} ref={ref}>
        {children}
    </div>
)
...
  • The component returns a <div> element with dynamic styles based on the intersecting state.
  • The transform property is used to apply translation based on the x and y values if we need element to slide from left to right top to bottom or vice versa.
  • CSS transition classes are conditionally applied to control opacity based on intersection such that when intersection is set to true it will transition to opacity to l00 else transition to opacity 0.

Step 3: Using Reveal Component

Now in our home page.tsx component we can use RevealComp to create scroll-reveal animations. For demo, we have two sections with images and text, each revealing/hiding as the user scrolls.

// src/app/page.tsx
import RevealComp from '@/components/reveal'
import Image from 'next/image'
import React, { useEffect, useRef, useState } from 'react'

export default function Home() {
    return (
        <div className='container px-6 max-w-4xl overflow-x-hidden mx-auto'>
            <h1 className='text-4xl font-bold mt-10'>ONScroll Animation</h1>
            <div className='grid grid-cols-2 gap-10 md:grid-cols-2 min-h-[70vh] items-center'>
                <div className='md:order-2'>
                    <RevealComp duration='300ms' threshold={1}>
                        <div className='relative w-full max-w-sm h-52 rounded overflow-hidden'>
                            <Image className='object-cover' fill src={"https://picsum.photos/seed//500/500"} alt='hero-image' ></Image>
                        </div>
                    </RevealComp>
                </div>
                <div className='md:order-1'>
                    <RevealComp y={-20} duration='300ms' threshold={0.9}>
                        <h2 className='text-xl md:text-4xl mb-4 font-bold'>Hello,World</h2>
                        <p className='max-w-sm text-sm'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla qui provident dolor beatae laborum, nisi laudantium tempora numquam aut molestiae velit eius minus. Odio voluptas at eveniet quasi, excepturi eaque?</p>
                    </RevealComp>
                </div>
            </div>

            <div className='grid gap-10 grid-cols-2 mt-20 md:grid-cols-2 min-h-[50vh] items-center'>
                <RevealComp x={-10} duration='300ms' threshold={0.75}>
                    <div className='relative w-full max-w-sm h-52 rounded overflow-hidden'>
                        <Image className='object-cover' fill src={"https://picsum.photos/seed/r2/500/500"} alt='hero-image' ></Image>
                    </div>
                </RevealComp>
                <div className='self-center'>
                    <RevealComp x={20} duration='200ms' threshold={0.75}>
                        <h2 className='text-xl md:text-4xl mb-4 font-bold'>Welcome</h2>
                        <p className='max-w-sm text-sm'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla qui provident dolor beatae laborum, nisi laudantium tempora numquam aut molestiae velit eius minus. Odio voluptas at eveniet quasi, excepturi eaque?</p>
                    </RevealComp>
                </div>
            </div>
        </div>
    )
}

The component returns a JSX structure containing a container with a specified maximum width, preventing horizontal overflow. Two grid layouts are created. The first grid has two columns, and the second grid has two columns with a larger top margin. Both grids use the RevealComp component for scroll-based animations. The RevealComp component is used with specified duration and threshold props. This encapsulates the content within the component for scroll-based animations. Similar structures are repeated with text content and images, each wrapped in the RevealComp for scroll-based animations.

Now you can run the Next JS app using npm run devVisit http://localhost:3000 in your browser to see the animation in action.

Congratulations! You’ve successfully implemented scroll-reveal animations in a Next.js application using Intersection Observer. This fundamental technique will help you in improving the visual appeal of your website, providing a more dynamic and engaging user experience. Feel free to experiment further, tweak the parameters, and integrate these techniques into your own projects. Happy coding!