Text✨ Premium

Wave Effect

Each letter waves vertically with a progressive CSS delay — JS splits the text into spans on the fly to animate each character independently.

CSSJSWave

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

Animated brand keyword on a landing page — Wave Effect example 1

① Animated brand keyword on a landing page

WhenThe main headline of a landing page needs to capture attention on load — a single large word, the animation living inside the text itself.
WhyThe letter-by-letter wave draws the eye without a background animation or canvas: the motion is in the message, not behind it. The text stays fully selectable.
SettingsFont 4–6 rem, duration 1.2 s (extend the `1s` value in `.text-wave span`), delay 0.12 s per letter for a natural rhythm on a large typeface.
Live e-sport score — winner reveal — Wave Effect example 2

② Live e-sport score — winner reveal

WhenA match result or the winning team's name appears at the end of a game — the letter-by-letter animation builds tension before the full reveal.
WhyThe progressive delay turns a plain score display into a suspenseful moment: the wave travels the name from left to right, mirroring the dramatic build before the official announcement.
SettingsFont 3–4 rem, duration 1 s (default), delay 0.12 s per letter (change 0.1 to 0.12 in the JS) to slightly stretch the suspense on a short word.
Music player — now playing track title — Wave Effect example 3

③ Music player — now playing track title

WhenA music app's playback screen shows the artist name or track title on a loop — the wave runs endlessly in visual sync with the music.
WhyThe <code>.text-wave-continuous</code> variant (2 s) creates a slow, hypnotic rhythm that blends with the musical mood; the letter-by-letter motion echoes a graphic equalizer without imitating one.
SettingsClass .text-wave-continuous (duration 2 s, keyframe waveContinuous, amplitude −15 px), font 2.5–3 rem, delay 0.1 s per letter (default) for a broad, steady wave.

How it works

The IIFE targets document.getElementById('waveText') and replaces its innerHTML with the string "WAVE" split letter by letter: each character becomes a <span> with style="animation-delay: Xs" computed as 0.1 × index seconds. The source text is hardcoded in the script; to use the element's existing content instead, replace "WAVE" with e.textContent.

The CSS rule .text-wave span applies animation: 1s ease-in-out 0s infinite wave and, critically, display: inline-block: without this, transform: translateY() has no effect on inline elements. The keyframe @keyframes wave moves each letter from translateY(0) to translateY(-15px) at mid-duration, then back to zero, creating an infinite sinusoidal vertical oscillation.

The progressive delay creates the wave illusion: letter 0 rises first, letter 1 follows 100 ms later, and so on. With a 4-letter word, the crest travels the full width in 300 ms before the first letter restarts — the wave appears to move continuously from left to right.

The CSS file includes two additional variants: .text-wave-v2 with @keyframes waveV2 (amplitude −10 px, fixed nth-child delays for 6 children) and .text-wave-continuous (2 s duration). The same IIFE also initializes waveTextV2 (text “BOUNCE”), flipText, and a hover scramble on scrambleTextHover — each with an early return if the element is absent from the DOM.

Accessibility

  • prefers-reduced-motion absent: no media query or JS check stops the animation when the OS signals this preference. Recommended fix: add @media (prefers-reduced-motion: reduce) { .text-wave span, .text-wave-v2 span { animation: none; } }.
  • Child <span> elements injected by JS carry no aria-hidden attribute — text content remains intact in the accessibility tree and readable by screen readers.
  • No keyboard interaction or focus management: the animation is purely decorative/presentational.
  • Text/background contrast is not defined by the effect; it depends entirely on the integration context. Verify a WCAG AA ratio ≥ 4.5:1 for normal text.
  • No rapid flickering: oscillation frequency is ~1 Hz, well below the 3 Hz threshold for photosensitive seizures (WCAG 2.3.1).

Browser compatibility

Requires only CSS animations (@keyframes, transform) and basic DOM manipulation — universal support in modern browsers.

Chrome 60+✓ Full
Firefox 55+✓ Full
Safari 12+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If JS is disabled, the text stays readable in its container but individual <code>&lt;span&gt;</code> elements are not created — the animation does not run. No JS errors, no network dependencies.

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" id="waveText">
  <!-- JS replaces the text with one &lt;span style="animation-delay: Xs"&gt; per letter -->
  <span style="animation-delay: 0s">W</span>
  <span style="animation-delay: 0.1s">A</span>
  <span style="animation-delay: 0.2s">V</span>
  <span style="animation-delay: 0.3s">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
Wave amplitude (@keyframes wave, 50%) -15px translateY value at the midpoint — -6 px for a subtle effect, -30 px for a dramatic bounce.
Animation duration (.text-wave span, animation) 1s First value of the animation shorthand — 0.6 s for a snappy effect, 2 s for a slow breath.
Per-letter delay step (JS, ${.1*t}) 0.1 Delay multiplier in the JS expression — 0.05 tightens the wave, 0.2 stretches it over more time.
Source text (JS, "WAVE") "WAVE" Hardcoded string in e.innerHTML — replace with e.textContent to animate whatever text is already in the HTML.
Easing (.text-wave span, animation) ease-in-out Timing function — linear for a mechanical feel, cubic-bezier(0.4,0,0.2,1) for a natural spring.
Reduced-amplitude variant (CSS class) .text-wave Switch to .text-wave-v2 (keyframe waveV2, amplitude -10 px) for subtler motion in compact contexts.
Font size (.demo-text, font-size) 2.5rem Override per context: 4–6 rem for a hero, 0.85–1 rem for a badge, 2.5–3 rem for an editorial heading.

FAQ

Yes. The JS injects <span> elements with no aria-hidden attribute — the text stays visible in the DOM and Googlebot reads it as plain text. No cloaking technique, no indexing issues.
The code targets getElementById('waveText') — one element only. For multiple instances, switch to document.querySelectorAll('[data-wave]') and iterate: for each element, split its textContent into <span> elements with the progressive delay. The logic is identical, just applied in a loop.
Without JS, the container stays a single text node — there are no child <span> elements for .text-wave span to target. Two solutions: include the JS (recommended) or manually split the HTML into <span style="animation-delay: Xs"> per letter.