Pattern · 29
Text split
拆字进场 · text-splitAnimate a headline by character or word while preserving one readable name.
A character entrance needs separate nodes, but those nodes can fragment what assistive technology reads. Keep the full sentence as the parent's aria-label and hide the visual pieces from the accessibility tree. Character splitting also needs care to avoid breaking a word across lines.
Formula
original = heading.textContent heading.ariaLabel = original parts = split(original, by) for part: span.text = part; span.ariaHidden = true delayᵢ = i × stagger
Parameters
| This page | Mature libraries | |
|---|---|---|
| by | char / word | GSAP SplitText: chars, words, lines |
| aria | full sentence on parent | GSAP aria: "auto" |
| stagger | 45 ms / part | — |
Doing it well
- Save the original text before splitting. GSAP SplitText's default
aria: "auto"puts that text on the parent'saria-label.GSAP src/SplitText.ts:355,373 - Mark visual fragments
aria-hiddenso a screen reader does not read each one separately.GSAP src/SplitText.ts:149,373 - For character motion, wrap each word as a unit so it does not break midway across lines.www-motion/rewrite/lib/detail.js:43–45
In our cases
19 studies split characters or words. cardwall, liquid-type, and type turn text into staggerable visual units; the rewritten detail title also wraps each word so characters do not break mid-word.
Used in 19 cases
Reference implementation · basics.js
/**
* Split an element's text into spans for per-character animation without breaking
* screen readers: the parent keeps the real text as aria-label, the pieces are aria-hidden.
*/
export function splitText(el, by = 'char') {
const text = el.textContent;
el.setAttribute('aria-label', text);
el.textContent = '';
const parts = by === 'word' ? text.split(/(\s+)/) : Array.from(text);
return parts.map(part => {
if (/^\s+$/.test(part)) { el.append(part); return null; }
const span = document.createElement('span');
span.textContent = part; span.setAttribute('aria-hidden', 'true'); span.style.display = 'inline-block';
el.append(span); return span;
}).filter(Boolean);
}