webdevpuneet.com

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

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

CategoryCards

Copy & paste

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>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), 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

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.

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.