webdevpuneet.com

Reveal on Scroll — Free HTML CSS JS Snippet

Reveal on Scroll · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="page">
  <h1>Scroll down ↓</h1>
  <div class="card fade-up"><div class="icon">🎯</div><h3>Precision</h3><p>Built for accuracy in every interaction.</p></div>
  <div class="card fade-up" style="transition-delay:0.1s"><div class="icon">⚡</div><h3>Speed</h3><p>Optimized for sub-100ms interactions.</p></div>
  <div class="card fade-up" style="transition-delay:0.2s"><div class="icon">🔒</div><h3>Security</h3><p>Enterprise-grade protection, zero config.</p></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

opacity: 0 + translateY(24px) starting state on .fade-up elements
transition: opacity 0.55s ease, transform 0.55s ease — CSS handles the animation
IntersectionObserver fires at threshold: 0.15 — 15% of element visible
obs.unobserve() ensures the animation fires exactly once per element
Stagger via inline transition-delay on each element
Asynchronous observer — does not block the main thread like scroll events
Works on any element — add .fade-up class and it is observed automatically
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Reveal on Scroll — IntersectionObserver, Fade-Up Transition & Stagger Delay

Screenshot of the Reveal on Scroll snippet rendered live

Reveal-on-scroll animations make content appear to rise into view as it enters the viewport. They are one of the most widely used micro-animations on landing pages and long-form content sites — each section fades up as the user scrolls down (pair with a split text headline reveal or a scroll-snap gallery), creating a sense that the page is being revealed progressively rather than all at once.

The .fade-up starting state

Elements with .fade-up start invisible and slightly below their final position: opacity: 0; transform: translateY(24px). Both have a CSS transition so changes animate smoothly: transition: opacity 0.55s ease, transform 0.55s ease.

The IntersectionObserver trigger

A single IntersectionObserver watches all .fade-up elements with threshold: 0.15 — the callback fires when 15% of the element is in the viewport. When e.isIntersecting is true, the observer adds .visible to the element: opacity: 1; transform: translateY(0). The CSS transition handles the animation. obs.unobserve(e.target) immediately disconnects the observer for that element — the animation fires once and never repeats.

Stagger delay

CSS transition-delay can be set via inline style on each element: style="transition-delay: 0.1s", 0.2s, 0.3s. This staggers the reveal so sibling elements animate in sequence rather than simultaneously — the same technique as the stagger list.

Why IntersectionObserver over scroll events

IntersectionObserver is asynchronous and runs off the main thread — it does not block rendering. Scroll events fire synchronously on every scroll tick and require manual threshold calculations. For reveal animations, IntersectionObserver is always the better choice.

Respecting reduced-motion preferences

Add @media (prefers-reduced-motion: reduce) { .fade-up { transition: none; transform: none; opacity: 1; } } to disable the animation for users who have requested reduced motion in their OS settings. This is important for accessibility compliance.

IntersectionObserver with threshold

The observer watches each .reveal element with threshold: 0.1 — the callback fires when 10% of the element is visible. Inside the callback, entry.isIntersecting determines if the element entered (add .visible) or left (optionally remove it for re-trigger). obs.disconnect() after the first trigger is intentional for most use cases — animations that re-play every scroll are typically more annoying than engaging.

The CSS animation on .visible

The .reveal class sets opacity: 0 and transform: translateY(30px) as the initial hidden state. The .visible class sets opacity: 1 and transform: translateY(0). CSS transition: opacity 0.6s ease, transform 0.6s ease handles the animation. The transition only fires when .visible is added — if .visible is set in HTML for above-the-fold content, no animation plays (the element starts visible).

Staggered children with animation-delay

For list items that should reveal one by one, add animation-delay: calc(var(--i) * 0.1s) where --i is a CSS custom property set as inline style on each child: style="--i:3". This creates a cascading reveal effect without JavaScript for each individual child.

Step by step

How to Use

  1. 1
    Scroll in the previewScroll down in the preview to see each card fade up from below as it enters the viewport. Each fires once via obs.unobserve.
  2. 2
    Add .fade-up to any elementIn the HTML panel, add class="fade-up" to any element you want to reveal on scroll. The JS observer picks it up automatically via querySelectorAll.
  3. 3
    Add stagger delayAdd style="transition-delay: 0.1s" to the second element, 0.2s to the third, etc. to stagger siblings in a grid or list.
  4. 4
    Change the trigger thresholdIn the JS panel, update 0.15 in { threshold: 0.15 } to control how far the element must be in view before revealing.
  5. 5
    Add reduced-motion fallbackIn the CSS panel, add @media (prefers-reduced-motion: reduce) { .fade-up { transition: none; transform: none; opacity: 1; } }.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Landing page section reveals
Apply .fade-up to each section as users scroll. Each section rises into view progressively, creating a sense of discovery rather than a static wall of content.
Feature card grid stagger animation
Add .fade-up and staggered transition-delay to each card in a grid. The cards reveal sequentially across the row as the section enters view.
Learn IntersectionObserver API
Edit the threshold and disconnect logic in the JS panel to understand how the observer fires. Change 0.15 to 0.5 and observe how the trigger point shifts.
Blog post and article section reveals
Apply to h2, paragraph, and image blocks in long-form content. The staggered reveal adds rhythm to reading without distracting from the content.
Replace ScrollReveal or AOS library
This pattern replaces ScrollReveal.js, AOS (Animate on Scroll), and similar libraries for basic reveal animations with zero dependencies.
Accessible reduced-motion fallback
Add @media (prefers-reduced-motion: reduce) { .fade-up { ... } } to disable the animation for users who need it. The content is always visible without animation.
Related: Scroll 3D Flip Reveal
See the Scroll 3D Flip Reveal for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Elements start with opacity: 0 and translateY(24px) — invisible and 24px below final position. An IntersectionObserver adds .visible when the element enters the viewport, triggering CSS transitions: opacity: 1 and translateY(0). The CSS handles the animation; JS only toggles the class.

IntersectionObserver runs asynchronously off the main thread and does not cause layout thrashing. Scroll events fire synchronously on every scroll tick and require manual getBoundingClientRect calls. For animations, IntersectionObserver is more performant and simpler to write.

obs.unobserve() disconnects the observer for that specific element after it has revealed. Without it, the observer would continue checking the element on every scroll tick, and if the element leaves and re-enters the viewport it would animate again.

Add style="transition-delay: 0.1s" to the second element, 0.2s to the third, 0.3s to the fourth, etc. Each element starts its animation after the specified delay, creating a sequential reveal across a row.

Add @media (prefers-reduced-motion: reduce) { .fade-up { opacity: 1; transform: none; transition: none; } } to the CSS. This immediately shows all fade-up elements without animation for users who have enabled reduced motion in their OS.

Yes. Click "JSX" for a React component. In React, use a useEffect with an IntersectionObserver. Attach a ref to the container, observe all .fade-up descendants, and clean up the observer on unmount.

Design your website