Keep SplitText line groups accurate by rebuilding them when text layout changes. Use automatic splitting for supported triggers, add explicit rebuilds for typography changes, and compare the result with unsplit browser text in your Mac desktop preview.
This guide is for someone editing a browser page with GSAP SplitText and a fluid heading. The task is to keep each generated line wrapper aligned with the words that currently belong on that visual line, including when the heading’s width stays fixed while its font size changes.
Why can fluid typography leave line groups behind?
A generated line wrapper encloses a particular group of words. After the available space or typography changes, that grouping may need rebuilding. GSAP documents autoSplit: true for font-loading completion and width changes when lines are included in the split. Those documented triggers do not establish a general watcher for every typography change. SplitText documentation
Consider a heading with font-size: clamp(2rem, 4vw, 4rem) inside a container capped at 42rem. MDN defines clamp() as a preferred value bounded by a minimum and maximum. Here, the preferred font size follows viewport width even after the container reaches its cap. clamp() CSS function
The following calculation uses an illustrative root font size of 16 CSS pixels and assumes enough available space for the container to reach its cap. These are calculated dimensions, not measured browser results or predicted line counts.
| Viewport width | Capped container width | Calculated font size |
|---|---|---|
| 900 CSS px | 672 CSS px | 36 CSS px |
| 1200 CSS px | 672 CSS px | 48 CSS px |
| 1600 CSS px | 672 CSS px | 64 CSS px |
The container remains the same width while the letters grow. The practical inference is that width monitoring alone does not cover this case. Treat the computed font-size change as another reason to rebuild, then check the resulting word placement.
Define accuracy against the browser’s unsplit rendering at the same dimensions and styles. Avoid requiring the heading to retain its original line count: a fluid layout is allowed to produce different lines.
Establish an unsplit reference before changing the animation
Create two copies of a short synthetic heading in your local preview. Give them the same width, font, size, weight, spacing and wrapping rules. Leave the first untouched and apply SplitText to the second. This temporary comparison makes a stale grouping visible without assuming where a particular word ought to wrap.
Begin with ordinary whitespace and wrapping. The W3C CSS Text specification defines how white-space controls whitespace collapsing and soft wrapping; it also defines separate line-breaking and spacing rules. A rebuild should preserve the intended rules rather than replace them with manually inserted breaks. CSS Text Module Level 3
For a page using web fonts, wait for document.fonts.ready before the first split. MDN documents that this promise resolves after the relevant font loading and layout operations finish. This establishes an initial measurement point; it does not freeze subsequent layout changes. FontFaceSet: ready property
Check the font actually used in the browser as well. MDN distinguishes used fonts from all declared fonts: an optional font, for example, may not be used if it misses its loading window. Waiting for readiness should not be interpreted as proof that every declared font became the rendered face. Document: fonts property
Implement automatic splitting plus an explicit fluid-size check
The following complete browser module creates an unsplit reference, an animated copy and a Toggle spacing button. Use a disposable preview page in an existing JavaScript project whose bundler resolves npm imports, supports top-level await, and runs its browser entry after the document body exists. Have Node.js and npm installed on your Mac.
Install GSAP from the project directory with the following command. Use GSAP 3.13 or later for this callback workflow. GSAP’s installation documentation covers npm installation and explicit plugin registration. Installation
npm install gsap
Replace the disposable preview’s browser entry module with this code. The initialization follows the documented SplitText.create() pattern. The fade uses gsap.from() to animate from zero opacity to the existing appearance. SplitText.create(), gsap.from()
import { gsap } from 'gsap';
import { SplitText } from 'gsap/SplitText';
gsap.registerPlugin(SplitText);
const style = document.createElement('style');
style.textContent = `
.flow-demo {
width: min(100%, 42rem);
font-family: system-ui, sans-serif;
}
.flow-demo h2 {
font-size: clamp(2rem, 4vw, 4rem);
line-height: 1.15;
font-weight: 600;
margin: 1rem 0;
white-space: normal;
text-wrap: wrap;
}
.flow-line { outline: 1px solid #aaa; }
`;
document.head.append(style);
const host = document.createElement('section');
host.className = 'flow-demo';
const reference = document.createElement('h2');
reference.textContent =
'Panel Alpha holds a series of soft shapes beside Panel Beta.';
const target = reference.cloneNode(true);
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Toggle spacing';
host.append(reference, target, button);
document.body.append(host);
await document.fonts.ready;
const split = SplitText.create(target, {
type: 'lines',
linesClass: 'flow-line',
autoSplit: true,
onSplit(self) {
return gsap.from(self.lines, {
opacity: 0,
duration: 0.3,
stagger: 0.04
});
}
});
let timer;
let fontSize = getComputedStyle(target).fontSize;
function checkFluidSize() {
clearTimeout(timer);
timer = setTimeout(() => {
const next = getComputedStyle(target).fontSize;
if (next !== fontSize) {
fontSize = next;
split.split();
}
}, 150);
}
window.addEventListener('resize', checkFluidSize);
let spaced = false;
button.addEventListener('click', () => {
spaced = !spaced;
reference.style.letterSpacing = target.style.letterSpacing =
spaced ? '0.06em' : 'normal';
split.split();
});
The animation is created inside onSplit() and returned so SplitText can clean it up and synchronize it across rebuilds. GSAP documents this callback workflow in version 3.13 and later. SplitText documentation
The added resize handler checks only the computed font size and requests a rebuild after resizing pauses. The 150-millisecond delay is an illustrative implementation choice. This example aims for accurate groups once resizing settles; it does not promise correct grouping during every intermediate resize frame.
The spacing button demonstrates a second explicit trigger. It changes both copies, then calls split.split(). GSAP documents that this method reverts the existing split first when necessary. A width change and font-size change can also cause overlapping rebuild requests in this small example; consolidate them if profiling shows unnecessary work in a larger page. split()
The module passed a JavaScript syntax check, and the illustrative dimension calculations were executed. Browser layout and animation were not executed here; complete the visual checks below in your local preview.
Choose rebuild triggers from what your layout changes
The sample watches one fluid font-size expression. When adapting it, identify the code or layout event that changes the text and put the rebuild at that boundary. Do not assume the sample detects unrelated class changes, container-driven typography or continuously animated font properties.
| What changes in your page | Suggested handling |
|---|---|
| Text element width | Enable the documented automatic splitting behavior and verify the result. |
| Viewport-driven font size at a fixed text width | Compare the computed size after resizing, then rebuild when it changes. |
| A control changes tracking, weight or font settings | Request a rebuild after the control applies the new styles. |
| Typography depends on a surrounding container | Observe that controlling container and check the relevant computed typography. |
| The component becomes visible after being hidden | Initialize or rebuild once it has its intended rendered dimensions. |
For container-driven layouts, ResizeObserver reports element-size changes independently of viewport resizing. Observe the container that supplies the sizing input. An observer on the text box alone cannot be treated as a notification for every computed-style change. ResizeObserver
Avoid rebuilding indiscriminately on every observed height change. Rebuilding can itself affect layout. MDN documents resize-observer cycles and explains that avoiding a feedback loop requires controlling the dependency; deferring work to another frame does not remove an underlying infinite cycle. Compare relevant inputs before requesting another rebuild. ResizeObserver observation errors
If the heading text changes, restore the old split before replacing the content, then initialize a new instance from the new source text. GSAP documents that revert() restores the original pre-split content. Editing generated wrappers and subsequently reverting risks discarding those edits. revert()
Verify wrapping without changing browser shaping
In your Mac browser, open the local preview and resize the window slowly, then release it. Compare the last word on each line in both copies after the rebuild and fade have settled. Repeat while the container is capped and while it is narrow enough to shrink. Click Toggle spacing at each size.
Use this diagnostic sequence when the copies disagree:
- Confirm that both copies have the same available width and computed typography.
- Rebuild at the current size. If that resolves the mismatch, investigate the missing trigger.
- If the mismatch remains, remove styling from generated line wrappers and test again.
- Restore unsplit text and verify that the underlying CSS produces the intended wrapping.
- Change one layout input at a time until the difference is reproducible.
Keep character shaping within the browser’s text engine. W3C specifies circumstances in which shaping must continue across inline boundaries and circumstances in which it must break, including separating inline padding, borders or margins. This means wrapper styling can affect the thing being measured. Avoid adding spacing between individual character boxes merely to reproduce an earlier screenshot. Shaping Across Element Boundaries
Do not count visual lines by splitting innerText at newline characters. WHATWG defines innerText as rendered text with collection rules for text, explicit breaks and structural boundaries; it does not provide an array of soft-wrapped visual lines. Keep the unsplit browser rendering as the comparison reference. The innerText and outerText properties
Respect the library’s documented limits: GSAP advises avoiding text-wrap: balance with splitting and says SplitText is not designed for SVG text nodes. For balanced headings, keep the text unsplit and animate the whole element; for line wrappers, use ordinary wrapping on an HTML text element. SplitText tips and limitations
Finish when the split copy follows the unsplit copy across your actual width and typography changes. Keep explicit rebuilds close to the changes that require them, retain the reference while diagnosing, and remove the temporary duplicate and line outlines before integrating the heading into the finished page.
Sources
- SplitText — GSAP
- SplitText.create() — GSAP
- split() — GSAP
- revert() — GSAP
- Installation — GSAP
- gsap.from() — GSAP
- clamp() CSS function — MDN Web Docs
- FontFaceSet: ready property — MDN Web Docs
- Document: fonts property — MDN Web Docs
- ResizeObserver — MDN Web Docs
- CSS Text Module Level 3 — W3C
- HTML Standard: The innerText and outerText properties — WHATWG