Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild

Scroll Reveal in React Without a Library You Will Regret

Sean

Platform Writer

Oct 15, 2026
7 min read

The clean way to do scroll-reveal in React is the Intersection Observer API: it tells you when an element enters the viewport without the performance cost of listening to every scroll event, and you can wrap it in a small reusable hook in about fifteen lines. You do not need a heavyweight animation library for the common case of fading content in as it scrolls into view - you need one hook and a CSS transition.

Scroll Reveal in React Without a Library You Will Regret

Reach for a big animation dependency and you ship kilobytes to do something the browser gives you for free. Here is the free version, and where a library is actually worth it.

Table of contents

Why Intersection Observer, not scroll listeners

The naive approach listens to the window scroll event and checks each element’s position on every pixel of scroll. That fires constantly, runs layout-reading code on the main thread, and is the classic cause of janky scrolling.

The Intersection Observer API flips it: you register the elements you care about, and the browser efficiently tells you when they cross a visibility threshold, off the main thread. It is purpose-built for exactly this - lazy loading, infinite scroll, and reveal-on-scroll - and it is in every current browser.

The practical upshot is smooth reveals even with many animated elements, because you are not doing position math on every scroll tick.

A reusable useReveal hook

The whole pattern fits in a small custom hook. You create a ref, attach an observer that sets an inView state when the element crosses a threshold, and return both:

import { useEffect, useRef, useState } from 'react';

export function useReveal(threshold = 0.2) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) { setInView(true); obs.disconnect(); } },
      { threshold }
    );
    obs.observe(el);
    return () => obs.disconnect();
  }, [threshold]);
  return { ref, inView };
}

Disconnecting after the first intersection means the element reveals once and stays revealed, which is what most designs want.

Wiring it to CSS

The hook only tracks visibility - the animation is plain CSS, which keeps it fast and themeable. Apply a base hidden state and a visible state toggled by the inView flag:

function Reveal({ children }) {
  const { ref, inView } = useReveal();
  return (
    <div ref={ref} className={inView ? 'reveal reveal--in' : 'reveal'}>
      {children}
    </div>
  );
}
.reveal { opacity: 0; transform: translateY(20px); transition: opacity .6s ease, transform .6s ease; }
.reveal--in { opacity: 1; transform: none; }

That is the entire feature. The browser does the compositing on the GPU, so it stays smooth.

Accessibility and the gotchas

Two things separate a tasteful reveal from an annoying one. Respect prefers-reduced-motion: wrap the transition in a media query so users who have asked for less motion simply see the content, no animation. Skipping this is a real accessibility failure, not a nicety.

Second, do not hide content from users who have JavaScript disabled or from crawlers behind opacity: 0 permanently - make sure the revealed state is the default if the observer never runs, or the content exists in the DOM regardless. A reveal is a presentation enhancement; the content must survive without it. A library is worth it only when you need orchestrated timelines, scroll-linked scrubbing, or physics - not for fade-in.

Where RunxBuild fits

A scroll-reveal is a front-end detail, and the site it lives on is either a static build or an app. Either way, RunxBuild deploys it from your GitHub repo - a static site with custom domains and 120GB of included bandwidth, or a full service if the React app has a backend - with build logs and a live route.

The animation ships as part of your bundle; nothing server-side is involved. What the platform gives you is the fast static delivery that makes the reveal feel instant, and a place to add the API behind the marketing site when the project grows past a single page.

How this fits the rest of the stack

The cost of a project is the sum of its line items, not the headline number, and the RunxBuild hosting calculator is the right place to add them up: the service, the database, the storage, the bandwidth, each a separate figure. Model it before committing, not after the first invoice. The RunxBuild dashboard is where the running project shows its real usage in one place.

Useful related references:

FAQ

How do I do scroll reveal in React?

Use the Intersection Observer API wrapped in a small custom hook: create a ref, observe the element, set an in-view state when it crosses a visibility threshold, and toggle a CSS class that transitions opacity and transform. No scroll event listeners and no heavy library are needed for the common fade-in case.

Is Intersection Observer better than scroll events?

For visibility detection, yes. Scroll-event listeners fire constantly and run layout math on the main thread, causing jank. Intersection Observer reports visibility efficiently and off the main thread, so reveals stay smooth even with many animated elements.

Do I need a library like Framer Motion for scroll reveal?

Not for a simple fade or slide in on scroll - a hook plus a CSS transition handles that. A dedicated animation library earns its weight only when you need orchestrated timelines, scroll-linked scrubbing, spring physics, or complex sequencing.

How do I make scroll animations accessible?

Respect the prefers-reduced-motion media query by disabling the transition for users who request reduced motion, and make sure the content is present and readable even if the observer never runs, rather than hiding it permanently behind opacity zero.

Does scroll reveal hurt SEO?

Not if the content is in the DOM regardless of the animation. Problems arise only when content is injected or kept hidden in a way crawlers cannot see. Keeping the markup present and using CSS purely for the visual reveal keeps it crawlable.

#react#scroll reveal#intersection observer#animation#frontend