webdevpuneet.com

Word Spring Bounce Heading — CSS Stagger Animation

Word Spring Bounce Heading · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<div class="scene">
  <h1 class="spring-heading" id="heading">Words that land with a bounce</h1>
  <button class="replay" onclick="playSpring()">Replay animation</button>
</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

Splits any headline into per-word spans via a single wrapWords() call
Four-stop keyframe fakes a spring overshoot without a physics library
cubic-bezier(0.34, 1.56, 0.64, 1) back-ease reinforces the keyframe overshoot
Per-word stagger via setTimeout at a fixed interval, not animation-delay
Replay button resets and retriggers the animation using a forced reflow
Words (not letters) keep each animated unit legible mid-motion
Zero dependencies — pure CSS keyframes triggered by class toggling
Works on any heading length; stagger count adapts automatically
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

Word Spring Bounce Heading — Staggered scale(0) Pop-In with Overshoot Easing

Screenshot of the Word Spring Bounce Heading snippet rendered live

A word spring bounce heading scales each word up from nothing with a slight overshoot before settling, one word after another, so the whole sentence feels like it is being popped into place rather than simply appearing. The spring overshoot — scaling past 1 before settling back — is what separates this from a plain fade-and-scale entrance and gives it the bouncy, playful energy associated with modern spring-physics motion libraries, without needing one. For a per-letter version of a staggered entrance, see split text or scroll letter stagger; for a scrambled-noise reveal instead of a scale-in, see text scramble.

Splitting the heading into words

wrapWords(el) splits the heading's text on whitespace with el.textContent.trim().split(/\s+/), then rebuilds the innerHTML as one <span class="word"> per word joined by literal spaces. Splitting on words rather than characters keeps each unit legible mid-animation and keeps the total number of staggered elements small even for a long headline — a sentence of 8 words only needs 8 timed delays instead of 40+ per-letter ones.

The spring keyframe without a physics library

@keyframes springPop fakes a spring curve using four keyframe stops instead of a real physics simulation: 0% starts at scale(0) and opacity: 0; 60% overshoots to scale(1.14) at full opacity; 80% undershoots slightly to scale(0.94); 100% settles at scale(1). Combined with cubic-bezier(0.34, 1.56, 0.64, 1) — a standard "back ease" curve whose second control point exceeds 1 — the overshoot in the keyframe and the overshoot in the easing function reinforce each other, producing a springier pop than either alone.

Staggering with setTimeout, not animation-delay

playSpring() loops over every .word and calls setTimeout(() => w.classList.add('pop'), i * 90), adding the .pop class (and therefore triggering the keyframe animation) at 90ms intervals per word index. Using setTimeout to add a class — rather than a CSS animation-delay baked into the stylesheet — makes the whole sequence replayable on demand: the "Replay animation" button forces a reflow with void w.offsetWidth after removing .pop from every word, which resets the animation state so classList.add('pop') can retrigger the keyframe from scratch.

Why the forced reflow matters

Removing and immediately re-adding the same CSS class in the same JavaScript tick does not restart a CSS animation, because the browser batches the style recalculation and never observes the class actually being absent. void w.offsetWidth forces the browser to synchronously compute layout in between the remove and the re-add, which is enough to make the animation replay reliably every time the button is clicked.

Step by step

How to Use

  1. 1
    Watch it load, then replayThe headline pops in word by word on page load. Click "Replay animation" to watch it again with a forced reflow reset.
  2. 2
    Change the headline textEdit the text inside #heading in the HTML panel — wrapWords() re-splits it into word spans automatically on load.
  3. 3
    Adjust the stagger speedIn the JS panel, change the 90 multiplier in setTimeout(() => w.classList.add("pop"), i * 90).
  4. 4
    Tune the bounce strengthIn the CSS panel, adjust the 1.14 and 0.94 scale values inside @keyframes springPop for a stronger or gentler overshoot.
  5. 5
    Change the easing curveSwap cubic-bezier(0.34, 1.56, 0.64, 1) on .word.pop for a different back-ease curve to make the settle snappier or softer.
  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

Playful product and app landing pages
The spring bounce reads as friendly and energetic, well suited to consumer apps, creative tools, and products that want to feel approachable rather than corporate.
Onboarding and empty-state headlines
Pop in a welcome headline or empty-state message with a bit of personality the first time a user sees a screen.
Learn faking spring physics with keyframes
Edit the keyframe percentages and scale values in the CSS panel to see exactly how overshoot-then-settle keyframes approximate a real spring curve without a JS physics engine.
Modal and toast headline entrances
Use the same word-pop technique on a modal title or success toast heading so it feels alive the instant it appears.
Pair with a scroll-triggered reveal
Wrap playSpring() in an IntersectionObserver callback (see char flip reveal for the pattern) to trigger the bounce when the heading scrolls into view instead of on page load.
Marketing announcement banners
Bounce in a limited-time offer or announcement headline to draw the eye more than a static banner would.

Got questions?

Frequently Asked Questions

A four-stop @keyframes animation moves scale from 0 to an overshoot of 1.14, back down to 0.94, then settles at 1. Combined with a cubic-bezier "back ease" curve whose control point exceeds 1, the visual result closely approximates a real damped spring without simulating one.

Removing a CSS class and re-adding it in the same synchronous JS tick does not restart the animation because the browser never registers the class as absent. Forcing a layout read with void w.offsetWidth between the remove and the re-add makes the browser compute style in between, so the re-added class reliably retriggers the keyframe.

Word-level staggering keeps the reveal count small and each animated chunk legible even mid-motion. Character-level staggering (see the char-flip-reveal-3d or split-text snippets) creates a denser, busier effect better suited to short, punchy headlines.

Remove the direct playSpring() call at the bottom of the JS and instead call it from an IntersectionObserver callback when the heading element becomes visible, matching the pattern used in the char-flip-reveal-3d snippet.

Yes — set the setTimeout multiplier to 0 to make every word pop in simultaneously, or increase it for a slower, more deliberate word-by-word reveal.

Yes. Click "JSX" for a React component. Split the heading string with .split(/\s+/) in a useMemo, render a span per word, and drive a "played" key or forced remount (changing the key prop) to replay the animation instead of manipulating classList directly.