IntersectionObserver Snippets — Free HTML CSS JS Scroll Reveal & Lazy Load Examples

50 snippets tagged IntersectionObserver · Live preview · Exports to React, Vue, Angular & Tailwind

What's included

Features

Fire-once reveals that unobserve after triggering — no repeated work
Staggered grid and list entrances driven by per-child delay
Lazy loading for images, iframes and heavy embeds below the fold
Scrollspy navigation that tracks the section currently in view
Infinite-scroll sentinels and stuck-state detection for sticky headers

About this tag

IntersectionObserver Snippets — 50 Free Scroll Reveal & Lazy Load Examples

IntersectionObserver lets the browser tell you when an element crosses a viewport threshold, instead of you asking on every scroll frame. That single inversion removes the layout thrash that makes hand-rolled scroll listeners janky, and it is why every reveal-style snippet in this library uses it.

The tag covers fire-once reveals and staggered grids, lazy loading of images and iframes, scrollspy navigation that highlights the section you are reading, infinite-scroll sentinels, and the "is this sticky header stuck yet" trick that has no other clean solution.

Push instead of poll

A scroll listener is a poll: you ask "are we there yet" on every single scroll event, and any layout read inside that handler — getBoundingClientRect, offsetTop — forces the browser to recompute layout synchronously, often dozens of times a second. An observer is a push: the browser does the intersection math on its own schedule, off the critical path, and calls your code only when a threshold is actually crossed. The snippets here never mix the two — once an observer is watching an element, no scroll listener duplicates that work.

Thresholds and rootMargin as tuning, not boilerplate

The threshold option controls how much of an element must be visible before it counts as intersecting, and rootMargin grows or shrinks the viewport box the observer checks against. A negative bottom margin is what makes a reveal fire once an element is meaningfully on screen rather than the instant its top pixel appears — a detail that is the difference between an animation that feels timed and one that feels premature.

Fire-once versus continuous watching

Most reveal and lazy-load snippets call unobserve on an element the moment they act on it, because re-triggering an entrance animation every time a user scrolls past it reads as a bug. Scrollspy and sticky-header detection are the exception — they need to keep watching indefinitely, since the answer to "which section is current" changes for as long as the page keeps scrolling.

The sentinel pattern

Infinite scroll and stuck-header detection both work by observing an invisible marker element rather than the content itself — a sentinel placed just before the end of a list, or a one-pixel element just above a sticky header. When the sentinel crosses the viewport, the observer fires, and the actual work (loading the next page, adding a "stuck" class) happens in response. It is a small indirection that turns two otherwise-awkward problems into the same well-understood technique.

Real-world uses

Common Use Cases

Reveal-on-scroll sections
The most common motion pattern on the web, done the way that does not fight the browser for main-thread time.
Lazy loading below the fold
Defer images, maps and video embeds until they are about to be seen, using rootMargin to start slightly early.
Scrollspy and reading progress
Highlight the current heading in a docs sidebar without measuring positions on every frame.
Replacing a scroll listener you already have
Most existing onscroll handlers map onto a threshold-based observer with less code and better frame times.

Got questions?

Frequently Asked Questions

A scroll handler runs on the main thread on every scroll event, and any layout read inside it (getBoundingClientRect, offsetTop) forces a synchronous reflow. The observer does its intersection math off the main thread and calls you only when a threshold is actually crossed — far fewer calls, and no forced layout.

It grows or shrinks the box the observer treats as the viewport. A rootMargin of "0px 0px -100px 0px" delays the callback until the element is a hundred pixels inside the viewport, which stops a reveal from firing while the element is still visually at the edge — the small tuning knob that makes reveals feel right.

Call observer.unobserve(entry.target) inside the callback as soon as you have applied the visible class. Otherwise the element re-animates every time it scrolls back into view, which looks like a bug to anyone scrolling up.

No. Every snippet is plain HTML, CSS and vanilla JavaScript that runs in any page — a static file, a WordPress theme, a Rails view, anything. When you do want a framework version, the editor exports each snippet as a React component, a React + Tailwind component, a standalone Tailwind HTML file, a Vue 3 single-file component or an Angular standalone component.

Yes — copy, modify and ship them in personal or commercial work, with no attribution required and no licence to track.

Yes. Every snippet opens in a live editor with separate HTML, CSS and JS panels and a preview that updates as you type. Check it at mobile, tablet and desktop widths, then copy the code or export it in your framework of choice.