Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 14.4Advanced ~21 min · shot 4 of 7

Flip and SplitText

In one line

Flip animates any layout change (Phase 10's FLIP, packaged): record, change the DOM, play. SplitText cuts a heading into lines, words and letters you can animate, and keeps it readable for screen readers.

0/7 · 0%

Think of it like this

A photographer's contact sheet. Photograph the table, move the cards however you like, photograph it again, and the editor slides each card from its old place to its new one. Flip is that editor. SplitText is scissors: it cuts a headline into pieces so each can move on its own.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Flip
A GSAP plugin that animates layout changes: it records positions, you change the DOM, and it plays the difference.
SplitText
A GSAP plugin that wraps a text's lines, words and letters in elements you can animate.
mask
A wrapper with overflow: clip around a piece, so it can slide in from outside its box.
aria-label
An attribute that gives an element the name a screen reader says. SplitText sets it to the full text.

Step by step

01Flip a layout and pause in the middle

In "Flip a list" press Row ⇄ column, pause and step. The four cards glide from the row to the column, and the track lists width, height, x and y (Flip also carries rotation and skewX, which stay 0 here) over 600 ms. Press Add: the new card grows from 0 to full size and fades in (onEnter), while the others make room. Press Hide first: that card shrinks and fades (onLeave) and the rest slide into its place. Show all brings it back the same way as Add.

02Read what SplitText did

In "Split the headline" the console says the h1's aria-label is the full sentence, there are 5 words and 23 letters, and each piece has aria-hidden="true". A screen reader reads the label once and skips the 28 pieces.

03Split it twice

Press Split again (one at a time, reading the console). The piece count stays 28 and the first split's letters are no longer on the page: the second split replaced them, so pressing Play now animates the detached letters (a track appears) while the visible ones do not move. With overwrite: false the count jumps to 74, because the new split wraps the old wrappers. Keep one split per element; re-split with revert() or autoSplit.

04Let the lines follow the width

Narrow the box holding a headline and it wraps to more lines. With autoSplit: true SplitText notices, splits again and runs onSplit again; if onSplit returns the tween, SplitText reverts it and starts the new one. In a test page this headline was 1 line wide and 3 lines narrow.

Try it yourself

  1. 1

    Take the accessibility off

    In "Split the headline" change aria: "auto" to aria: "none". The console now prints aria-label on the h1: null and the pieces have no aria-hidden (null). The page looks the same, but the heading no longer has a single name. We did not test real screen readers here: the accessibility attributes are what changed.

  2. 2

    Split only letters

    Change type: "words,chars" to type: "chars". The console says words: 0 | chars: 23 and then fails reading the first word's attribute: split.words[0] is undefined, so ask only for the pieces you split.

Code & diagrams

Flip a list

One `flip()` helper wraps every change. Try all four buttons, then slow the timeline down and step through the first one.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

cards: 4 | Flip.getState saved the position and size of each
Split the headline

Press Play for the letters to rise. Then press Split again and read the console, and Revert.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

aria-label on the h1: "Ride the hills of Ardenvale"
words: 5 | chars: 23
a letter's aria-hidden: true | a word's: true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Rebuild the elements with innerHTML

Change the Reverse button's handler to rebuild the list from text: list.innerHTML = cards().reverse().map((c) => '<div class="c">' + c.textContent + '</div>').join("").

terminal
$ Press Reverse and read the tracks, then the first card at 0 ms
── what you'll see ──
GSAP div.c, div.c, div.c [opacity, scale, parent] … 400 ms (no width, height, x or y track)
first card: opacity 0, transform matrix(0, 0, 0, 0, 12, 42) (every card pops in with onEnter; none of them slides)

Myth vs fact

Myth

Splitting text into letters is only visual, so it cannot hurt accessibility.

Fact

Without aria, assistive technology can read a split heading letter by letter, or lose the sentence. SplitText's aria: "auto" keeps one name on the element.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Flip's default animates width and height to keep children undistorted, and those are layout cost in Chromium (Topic 6.2). Use scale: true for plain shapes, and object-fit instead of scaling images.

  • ▸

    SplitText measures lines from the real layout, so fonts matter: a web font that loads after the split changes the lines. autoSplit: true listens to document.fonts and the width, and onSplit builds the animation again with each split.

Remember this

  1. 1

    The problem this solves: Phase 10 wrote FLIP by hand. Real pages need the awkward cases too: elements that enter or leave, nested children, scaled parents and interrupted moves, which Flip handles. Cutting text into letters by hand breaks screen readers, selection and line breaks; SplitText does it and handles the accessibility.

  2. 2

    Flip in three lines. const state = Flip.getState(targets) records each element's position and size (First). You change the DOM any way you like (Last). Flip.from(state, { duration: 0.6 }) inverts and plays: each element appears at its old place and slides to the new one. It returns a timeline. onEnter runs for elements that were not in the state (a new card, or one that was display: none), and onLeave for elements that are now hidden. Two details: pass targets: ".c" to Flip.from so Flip looks the elements up again and finds the new one, and hide a leaving element (hidden, display: none) instead of removing it, because a removed node is gone before Flip can fade it.

  3. 3

    What Flip animates. By default Flip tweens width and height (layout work, like Phase 10's slow version). scale: true uses scale instead, which is cheap but stretches children, so use it only for plain boxes. absolute: true takes elements out of the flow during the move so that neighbours close up correctly. Add data-flip-id to elements that are recreated (a framework re-render) so Flip knows the new one is the old one.

  4. 4

    SplitText. SplitText.create(el, { type: "lines,words,chars" }) wraps pieces in divs and returns .lines, .words and .chars arrays. mask: "lines" adds an overflow-clip wrapper per line so a line can rise out of a slot. aria: "auto" (the default) puts the full text in aria-label on the element and hides every piece with aria-hidden="true", so the heading is still read as one sentence.

  5. 5

    Splitting twice, and resizing. SplitText reverts an earlier split of the same element before splitting again (overwrite defaults to true), so the first split's pieces leave the page and any tween on them animates nothing visible. Line splits depend on width: autoSplit: true (it only matters when type includes lines; the default type is "chars,words,lines") re-splits when the width or the fonts change the lines, and expects onSplit to return the animation so it can be reverted and restarted. revert() restores the original text.

Explain it without notes

01

How do you animate a list re-order with Flip, and what does absolute: true do?

02

What does SplitText's aria: "auto" do, and why is it needed?

Practice

01

Animate a filter: hide the cards that do not match with Flip so the remaining ones slide to close the gap, with the others fading out.

02

Split the hero headline by lines with a mask so each line rises into view, and make it re-split on resize.

Done when you can

  • You can flip a layout change with getState and from, including enter and leave

  • You know why recreated elements need data-flip-id

  • You split text with aria handled, and can revert it