Text✨ Premium

Wave Animation V2

Letters in a wave with a 100 ms per-rank stagger: @keyframes waveV2 pure CSS, JS injects delays for any text length, zero dependencies.

CSSAnimationWave

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →

3 usage examples

Portfolio landing — Animated full-screen headline — Wave Animation V2 example 1

① Portfolio landing — Animated full-screen headline

WhenThe homepage of a portfolio or creative agency opens on a name, specialty, or tagline in uppercase letters.
WhyLetter-by-letter waving holds attention without overwhelming the message — no canvas, no overlay, just text. The typographic hierarchy stays intact.
SettingsFont-size 4–6 rem, font-weight 800, 0.12 s stagger (edit the 0.1 in the JS), amplitude −16 px in @keyframes waveV2.
App interface — Animated processing status label — Wave Animation V2 example 2

② App interface — Animated processing status label

WhenA SaaS dashboard shows a <strong>PROCESSING</strong>, <strong>ANALYSING</strong>, or <strong>LOADING</strong> label during a long task, replacing a spinner.
WhyThe wave gives elegant, readable feedback without locking attention on a rotating icon — swap the label text when the task ends to stop the animation.
SettingsFont-size 1–1.4 rem, 0.08 s stagger (short text 4–8 letters), amplitude −6 px to stay subtle in a dense UI.
Game intro or title sequence — One-shot cinematic reveal — Wave Animation V2 example 3

③ Game intro or title sequence — One-shot cinematic reveal

WhenA game title, series name, or app name appears at launch on a dark background: each letter enters with a wave, played once, like a film title card.
WhyOne-shot mode (<code>animation-iteration-count: 1</code>) delivers a dramatic entrance without a continuous loop — the wave plays once at launch, stops, and leaves the title flat and readable.
SettingsFont-size 5–7 rem, font-weight 900, letter-spacing 0.1–0.15 em, 0.15 s stagger (slow build on a short word), amplitude −20 px in @keyframes waveV2, replace infinite with animation-iteration-count: 1 on .text-wave-v2 span.

How it works

The animation relies on @keyframes waveV2: a simple translateY(0) → translateY(−10 px) → translateY(0) over 1 s, ease-in-out, looping infinitely. Each letter is a <span> with display: inline-block — required for transform to apply on inline elements.

The wave emerges from progressive stagger between letters: the JS computes animation-delay: 0.1s × index and injects it as a style attribute on each <span> generated via "BOUNCE".split("").map((ch, i) => ...). The CSS also declares delays via :nth-child(1..6) — a fallback when JS is absent or spans are pre-built in HTML.

The JS IIFE shares its scope with other effects on the same page (#waveText, #flipText, #scrambleTextHover) using sequential guards: if any getElementById returns null, the function exits immediately. For isolated use, pre-built spans in HTML with the correct inline animation-delay values are sufficient — the animation runs 100% in CSS, no JS needed.

To change the animated text, update the "BOUNCE" string in the JS, or pre-build your <span> elements directly in HTML with style="animation-delay: Ns". Beyond 6 letters, only the JS applies correct delays to additional characters — the :nth-child CSS rules cover only the first 6.

Accessibility

  • ⚠️ prefers-reduced-motion not checked in the code: the animation runs unconditionally. For production, add @media (prefers-reduced-motion: reduce) { .text-wave-v2 span { animation: none } }.
  • No aria-label on the container: screen readers may read letter by letter. Add aria-label="YOUR TEXT" on the parent <span> and aria-hidden="true" on each letter child.
  • Excellent contrast: white text (#fff) on dark background (#0a0a0f), ratio > 18:1, WCAG AAA — holds for any light color on a dark ground.
  • No keyboard interaction or focus management — purely decorative effect, no focus handling required.
  • Without CSS, the text remains visible as a flat word: readability is preserved.

Browser compatibility

CSS animation + transform: translateY and display: inline-block — supported in all modern browsers. The JS IIFE is plain vanilla (getElementById, innerHTML, split).

Chrome 88+✓ Full
Firefox 87+✓ Full
Safari 15+✓ Full
Edge 88+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If JS is absent or errors out, pre-built <code>&lt;span&gt;</code> elements in HTML keep their inline <code>animation-delay</code> and continue animating via CSS. If CSS is disabled, text displays flat and remains readable.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<span class="demo-text text-wave-v2" id="waveTextV2"
  aria-label="BOUNCE">
  <!-- 1 <span> per letter — 0.1 s progressive stagger -->
  <span aria-hidden="true" style="animation-delay: 0s">B</span>
  <span aria-hidden="true" style="animation-delay: 0.1s">O</span>
  <span aria-hidden="true" style="animation-delay: 0.2s">U</span>
  <span aria-hidden="true" style="animation-delay: 0.3s">N</span>
  <span aria-hidden="true" style="animation-delay: 0.4s">C</span>
  <span aria-hidden="true" style="animation-delay: 0.5s">E</span>
</span>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
translateY(−10px) in @keyframes waveV2 −10 px Vertical amplitude: raise to −20 px for a bold wave, drop to −5 px for a subtle shimmer.
animation: 1s ease-in-out … on .text-wave-v2 span 1 s Duration of one full cycle. 0.6 s = quick nervous wave, 2 s = slow and fluid motion.
ease-in-out in animation (timing-function) ease-in-out Speed curve per letter. Try cubic-bezier(.4,0,.6,1) for a more pronounced bounce.
0.1 in the JS (${.1*t}s) 0.1 s Per-letter stagger delay. 0.05 s = tight fast wave, 0.2 s = wide spread across a long word.
font-size: 2.5rem on .demo-text 2.5 rem Text size — the wave is most impressive at large sizes (4 rem+). Override freely via CSS on the parent.
font-weight: 800 on .demo-text 800 Weight — drop to 400–600 for a light editorial title, keep 700–900 for a strong graphic impact.
letter-spacing: −0.02em on .demo-text −0.02 em Letter spacing. Set to 0.08–0.15 em on short uppercase text to open up the wave.
color on .demo-text or its parent inherited (#fff) Text color — no dedicated CSS variable; override via color CSS on the element or a parent.

FAQ

Yes. The JS split("") rebuilds spans for any length. Beyond 6 letters, the CSS :nth-child rules cover only the first 6 — letters beyond that have no CSS delay. If you pre-build spans without JS, add inline animation-delay manually for each extra letter.
Replace infinite with 1 in animation-iteration-count on .text-wave-v2 span. To trigger the wave on scroll, remove the class text-wave-v2 on load and add it back via an IntersectionObserver when the element enters the viewport — the wave plays once then stops.
Yes, partially. Pre-built <span> elements in HTML with inline animation-delay keep animating in pure CSS even if JS is absent. Only the dynamic text rebuild (innerHTML) is lost. For a 100% CSS approach, pre-build your spans manually — sufficient for short, static text.