WOW.js Scroll Reveal Cards — Scroll-Triggered Animation Snippet

WOW.js Scroll Reveal Cards · Cards · Plain HTML, CSS & JS · Live preview

What's included

Features

Zero-JS orchestration
The stagger, easing, and duration are all declared via classes and data attributes, not JS code.
Fires once by default
iteration defaults to 1, so scrolling back up never replays the entrance.
Per-element delay attribute
data-wow-delay is read straight off the DOM node, so markup and timing stay together.
Configurable offset
The offset option shifts how far into the viewport an element must scroll before revealing.
Mobile-aware
The mobile option can disable the reveal watch on small screens to save battery.
Live DOM re-scan
A MutationObserver under live: true picks up cards added after init() automatically.
Works with any animate.css class
fadeInUp, zoomIn, flipInX, bounceIn — WOW.js triggers whichever class is present.
No animation code to maintain
All motion values live in animate.css, so updating the library updates every reveal.

About this UI Snippet

WOW.js Scroll Reveal Cards — What WOW.js Actually Does

Screenshot of the WOW.js Scroll Reveal Cards snippet rendered live

WOW.js is one of the smallest libraries you'll ever wire up, and that's the whole point of studying it: it does not animate anything. Every keyframe, every easing curve, every timing value in this snippet comes from animate.css. WOW.js's entire job is watching the page scroll and deciding, element by element, *when* to let those keyframes run.

The class-toggle trick

animate.css ships a rule that any element with both .animate__animated and a specific animation class (like .animate__fadeInUp) starts playing immediately on paint. That's a problem for scroll reveals — you don't want the animation to fire the moment the page loads, you want it to fire when the element scrolls into view.

WOW.js solves this the same way animate.css itself recommends: it ships a modifier class, .wow, that pairs with a CSS rule setting visibility: hidden on anything still carrying it. Every card in this grid starts as class="wow animate__animated animate__fadeInUp" — the animation classes are present from the start, but .wow keeps the element invisible and the animation un-started. When WOW.js's scroll listener detects the card has crossed into the viewport, it simply removes .wow. Visibility flips to visible, animate.css's animation rule (now unobstructed) starts playing, and the card fades and slides in exactly as animate.css defines it. WOW.js never touched transform, opacity, or a single style property — it flipped one class.

Predating IntersectionObserver

WOW.js was written in 2013, years before IntersectionObserver existed as a browser API. To know when an element enters the viewport it does the manual version of the same job: on scroll (throttled with requestAnimationFrame) it walks every .wow element still being watched, reads getBoundingClientRect(), and compares the element's top edge against window.innerHeight minus the configured offset. Cross that line and the element gets .wow removed and is dropped from the watch list (unless configured to fire repeatedly). It is, functionally, a hand-rolled visibility observer — which is exactly why modern code reaches for IntersectionObserver directly, and why WOW.js is worth understanding as a stepping stone rather than a first choice for new projects.

Where the stagger actually comes from

Nothing in the JavaScript orchestrates the 0.15s gaps between cards. Each card carries its own data-wow-delay="0.15s" attribute, and WOW.js reads that attribute off the element the moment it decides to reveal it, applying it as an inline animation-delay before removing .wow. Because the delay is baked onto the markup rather than computed in a loop, the stagger survives cards being added, removed, or reordered with no JS changes at all — it's a pure HTML-authoring concern.

The live option and dynamic content

live: true (the default) makes WOW.js re-scan the DOM with a MutationObserver so cards injected after init() — from an AJAX call or a "load more" button — are picked up automatically. Set it to false in performance-sensitive pages where you know the reveal set is fixed at load time.

Build with AI

Build, Understand, Optimize, and Extend It With AI

This snippet is a good one to interrogate about the boundary between "what the JS library does" and "what the CSS library does," since that split is exactly where WOW.js's whole design lives. Paste the code into an AI assistant like Claude and ask it to trace, step by step, what happens between the page loading and a card's entrance animation completing — where visibility is controlled, where the delay value is read, and which library owns which part. Then ask what would happen if you removed animate__animated from a card but left .wow and animate__fadeInUp — a good way to understand why all three classes are required together. To extend it: ask for a version that re-reveals cards every time they scroll back into view, one that computes data-wow-delay automatically from each card's grid column and row instead of hand-writing it, or a version ported to plain IntersectionObserver with no WOW.js dependency at all, so the comparison is concrete rather than theoretical.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a scroll-reveal card grid using WOW.js (v1.1.2, from a CDN) and animate.css (v4, from a CDN) in plain HTML, CSS, and JavaScript.

Requirements:
- Lay out a responsive grid of at least 6 cards, each with a short heading and description, styled as a dark premium panel.
- Each card's HTML must carry three classes together: wow (WOW.js's hide-until-revealed marker), animate__animated (animate.css's base class), and an animate.css effect class such as animate__fadeInUp.
- Each card must also carry its own data-wow-delay attribute (e.g. "0.15s", "0.3s", "0.45s") so the cards reveal in a staggered sequence purely from markup, with no JavaScript computing the stagger.
- Initialize WOW.js with new WOW({ boxClass: 'wow', animateClass: 'animate__animated', offset: 0, mobile: true, live: true }).init() and explain in a comment that WOW.js does not animate anything itself — it only removes the .wow class (which keeps elements hidden via a visibility: hidden rule) when an element's bounding rect crosses into the viewport, letting the already-present animate.css classes run their own @keyframes.
- Make the page tall enough (extra bottom padding) that the cards start below the fold and must be scrolled to, so the reveal is actually demonstrated.
- Keep the JS to library initialization only — no manual scroll listeners or IntersectionObserver code, since WOW.js already handles that internally.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="wsr-stage">
  <div class="wsr-head">
    <span class="wsr-tag">WOW.js · animate.css</span>
    <h2>Scroll down to reveal the grid</h2>
    <p>Each card is invisible until it enters the viewport, then WOW.js fires its staggered animate.css class.</p>
  </div>
  <div class="wsr-grid">
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0s">
      <div class="wsr-icon">01</div>
      <h3>Instant Setup</h3>
      <p>Drop in one script tag and add a class. No build step, no config object required to start.</p>
    </div>
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0.15s">
      <div class="wsr-icon">02</div>
      <h3>CSS-Owned Motion</h3>
      <p>Every visual detail of the animation lives in animate.css — WOW.js never touches a style property.</p>
    </div>
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0.3s">
      <div class="wsr-icon">03</div>
      <h3>Fires Once</h3>
      <p>By default a card animates in a single time — scrolling back up won't replay it.</p>
    </div>
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0.45s">
      <div class="wsr-icon">04</div>
      <h3>Per-Card Timing</h3>
      <p>data-wow-delay is read straight off each element, so a grid can stagger without any JS loop.</p>
    </div>
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0.6s">
      <div class="wsr-icon">05</div>
      <h3>Offset Aware</h3>
      <p>An element only counts as "in view" once it clears the configured pixel offset from the bottom edge.</p>
    </div>
    <div class="wsr-card wow animate__animated animate__fadeInUp" data-wow-delay="0.75s">
      <div class="wsr-icon">06</div>
      <h3>Any animate.css Class</h3>
      <p>Swap animate__fadeInUp for animate__zoomIn or animate__flipInX — WOW.js doesn't care which one.</p>
    </div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Add both CDN fileswow.min.js for the JS, animate.min.css for the keyframes — WOW.js needs animate.css to have anything to trigger.
  2. 2
    Mark elements as wow + animate.cssGive each card class="wow animate__animated animate__fadeInUp" — all three classes are required.
  3. 3
    Stagger with data-wow-delayAdd data-wow-delay="0.15s" per card; WOW.js reads it and applies it as the animation-delay.
  4. 4
    Call new WOW().init()One line, run after the DOM is ready — it starts the scroll watch immediately.
  5. 5
    Scroll to triggerCards stay hidden until their top edge crosses into the viewport, then reveal once.
  6. 6
    Swap the animation classReplace animate__fadeInUp with any other animate.css class to change the reveal style per card.

Real-world uses

Common Use Cases

CARD
Feature grids
Reveal a row of product or feature cards as the visitor scrolls down a landing page.
Marketing sections
Stagger in pricing tiers or testimonial blocks without hand-writing keyframes.
Legacy jQuery-era sites
A drop-in reveal library for projects already built around class-based markup.
Teaching scroll triggers
A clear example of the class-toggle pattern before introducing IntersectionObserver.
Onboarding walkthroughs
Reveal steps one at a time as the user scrolls through an explainer page.

Got questions?

Frequently Asked Questions

No. WOW.js only adds and removes CSS classes. Every visual property — opacity, transform, timing — comes from animate.css's own @keyframes rules. WOW.js's job is purely deciding when an element has scrolled into view.

WOW.js ships a small stylesheet rule setting visibility: hidden on any element still carrying .wow. Since the animate.css classes are already present on the element, removing .wow is the only thing standing between the card and its entrance animation.

Each card has its own data-wow-delay attribute. When WOW.js reveals a card it reads that attribute and applies it as an inline animation-delay before removing .wow, so the stagger is authored in HTML, not computed in JavaScript.

For new code, IntersectionObserver is usually the better choice — it is more efficient and native to the browser. WOW.js predates it and re-implements the same idea with scroll-position math, which makes it a useful library to study but a legacy choice for production.

Not by default. WOW.js reveals each element once and stops watching it. Passing iteration or removing the once-only tracking via a custom callback is required to make it repeat.

Yes — swap the animation class in the HTML (animate__fadeInUp, animate__zoomIn, animate__flipInX, etc). WOW.js doesn't read or care which animate.css class is present, it only toggles .wow.