GSAP Text — SplitText + ScrambleText
Flow: gsap-setup → gsap-animate → gsap-text → gsap-optimise → gsap-test
Key files: composables/useReveal.js, plugins/gsap.js, assets/css/tailwind.css
Companion: For SplitText/ScrambleText API reference, invoke gsap-plugins. This skill covers text animation recipes only. Requires: greensock/gsap-skills
1. SplitText Setup
// Preferred — useReveal handles everything:
const { init, hero, scroll, split } = useReveal(sectionRef)
onMounted(() => init(() => scroll()))
// Manual setup:
const { $gsap: gsap, $lazyLoadSplitText, $lazyLoadScramble } = useNuxtApp()
let SplitText, ctx
const splits = []
onMounted(async () => {
await document.fonts.ready // CRITICAL: fonts before split
SplitText = await $lazyLoadSplitText()
ctx = gsap.context(() => { /* ... */ }, scopeRef.value)
})
onUnmounted(() => {
splits.forEach(s => s.revert())
splits.length = 0
ctx?.revert()
})
Creating a split
const s = SplitText.create(el, { type: 'words', mask: 'words' })
splits.push(s)
gsap.set(s.words, { y: '100%' })
gsap.set(el, { visibility: 'visible' })
2. Masked Word Slide-Up (Production Pattern)
The standard heading reveal used across 25+ components.
ctx = gsap.context(() => {
const s = SplitText.create(el, { type: 'words', mask: 'words' })
splits.push(s)
gsap.set(s.words, { y: '100%' })
gsap.set(el, { visibility: 'visible' })
gsap.to(s.words, {
y: '0%', duration: 0.8, ease: 'power4.out', stagger: 0.06, force3D: true,
scrollTrigger: {
trigger: el, start: 'top 85%',
toggleActions: 'play none none reverse', invalidateOnRefresh: true,
},
})
}, scopeRef.value)
Via useReveal (preferred)
<div ref="sectionRef">
<div class="reveal">
<h2 class="text-reveal">HEADING TEXT</h2>
</div>
</div>
const { init, scroll } = useReveal(sectionRef)
onMounted(() => init(() => scroll()))
3. ScrambleText Decode
See gsap-plugins skill for ScrambleText API reference. Recipe:
gsap.to(el, {
duration: 0.8 + el.textContent.length * 0.005,
scrambleText: { text: 'FINAL TEXT', chars: '▓░▒█▀▄╬╠╣▐▌■□', revealDelay: 0.3, speed: 0.4 },
})
4. Gotchas Summary
See references/learnings.md for full details and code examples.
- Await
document.fonts.ready before SplitText.create()
autoSplit: true is for lines only (responsive reflow) — words/chars don't need it. Always pair with onSplit()
- Parent autoAlpha + child mask: use
visibility, not autoAlpha
- Never blank
textContent before scramble
- Set
overwrite: false on colocated per-word tweens
- SplitText preserves
<span> — use :deep(div) for gradient styles
- CSS
.reveal + .text-reveal pre-hide classes prevent FOUC
5. useReveal Composable Reference
| Method |
Description |
init(fn) |
Async: waits for fonts, lazy-loads SplitText + Scramble, creates gsap.context |
hero(selector?, overrides?) |
Immediate reveal for above-fold. Auto-detects .text-reveal children |
scroll(selector?, overrides?) |
Per-element ScrollTrigger reveal with .reveal + .text-reveal |
split(target) |
SplitText wrapper: creates masked word split, tracks for cleanup |
// Hero (above-fold, immediate)
const { init, hero } = useReveal(sectionRef)
onMounted(() => init(() => hero('.reveal', { textDelay: 0.2 })))
// Scroll (below-fold, per-element ScrollTrigger)
const { init, scroll } = useReveal(sectionRef)
onMounted(() => init(() => scroll('.reveal', { start: 'top 75%', once: true })))
Cleanup is automatic — onUnmounted reverts gsap.context and all SplitText instances.
References
references/text-patterns.md — Combined clip+scramble, kinetic character split, elastic type assembly, rolling text (3D slot machine), horizontal text (containerAnimation), masked lines reveal, autoSplit with ScrollTrigger
references/learnings.md — 7 critical gotchas from production debugging
1---2name: gsap-text3description: Production recipes for GSAP text animations using SplitText and ScrambleTextPlugin. Companion to official gsap-plugins skill (API reference). Triggers: SplitText, split text, text animation, word animation, scramble text, ScrambleTextPlugin, text reveal, masked text, clip reveal, word stagger, text stagger, character animation, char split, kinetic text, elastic type, text decode, terminal text, cyber text, glitch text, chainTextReveal, useReveal text, GSAP text, Vue text animation, Nuxt text animation. Non-triggers: Not for scroll-driven animation without text (use gsap-scroll), mouse interactions (use gsap-interact), SVG animation (use gsap-svg), or general visual effects (use gsap-vfx). Outcome: Produces text animations — masked word reveals, scramble decodes, kinetic splits, and the useReveal composable API.4---56# GSAP Text — SplitText + ScrambleText78> **Flow**: gsap-setup → gsap-animate → **gsap-text** → gsap-optimise → gsap-test9> **Key files**: `composables/useReveal.js`, `plugins/gsap.js`, `assets/css/tailwind.css`1011> **Companion**: For SplitText/ScrambleText API reference, invoke **gsap-plugins**. This skill covers text animation recipes only. Requires: `greensock/gsap-skills`1213---1415## 1. SplitText Setup1617```js18// Preferred — useReveal handles everything:19const { init, hero, scroll, split } = useReveal(sectionRef)20onMounted(() => init(() => scroll()))2122// Manual setup:23const { $gsap: gsap, $lazyLoadSplitText, $lazyLoadScramble } = useNuxtApp()24let SplitText, ctx25const splits = []2627onMounted(async () => {28 await document.fonts.ready // CRITICAL: fonts before split29 SplitText = await $lazyLoadSplitText()30 ctx = gsap.context(() => { /* ... */ }, scopeRef.value)31})3233onUnmounted(() => {34 splits.forEach(s => s.revert())35 splits.length = 036 ctx?.revert()37})38```3940### Creating a split4142```js43const s = SplitText.create(el, { type: 'words', mask: 'words' })44splits.push(s)45gsap.set(s.words, { y: '100%' })46gsap.set(el, { visibility: 'visible' })47```4849---5051## 2. Masked Word Slide-Up (Production Pattern)5253The standard heading reveal used across 25+ components.5455```js56ctx = gsap.context(() => {57 const s = SplitText.create(el, { type: 'words', mask: 'words' })58 splits.push(s)59 gsap.set(s.words, { y: '100%' })60 gsap.set(el, { visibility: 'visible' })6162 gsap.to(s.words, {63 y: '0%', duration: 0.8, ease: 'power4.out', stagger: 0.06, force3D: true,64 scrollTrigger: {65 trigger: el, start: 'top 85%',66 toggleActions: 'play none none reverse', invalidateOnRefresh: true,67 },68 })69}, scopeRef.value)70```7172### Via useReveal (preferred)7374```html75<div ref="sectionRef">76 <div class="reveal">77 <h2 class="text-reveal">HEADING TEXT</h2>78 </div>79</div>80```8182```js83const { init, scroll } = useReveal(sectionRef)84onMounted(() => init(() => scroll()))85```8687---8889## 3. ScrambleText Decode9091See **gsap-plugins** skill for ScrambleText API reference. Recipe:9293```js94gsap.to(el, {95 duration: 0.8 + el.textContent.length * 0.005,96 scrambleText: { text: 'FINAL TEXT', chars: '▓░▒█▀▄╬╠╣▐▌■□', revealDelay: 0.3, speed: 0.4 },97})98```99100---101102## 4. Gotchas Summary103104See `references/learnings.md` for full details and code examples.105106- Await `document.fonts.ready` before `SplitText.create()`107- `autoSplit: true` is for **lines** only (responsive reflow) — words/chars don't need it. Always pair with `onSplit()`108- Parent autoAlpha + child mask: use `visibility`, not `autoAlpha`109- Never blank `textContent` before scramble110- Set `overwrite: false` on colocated per-word tweens111- SplitText preserves `<span>` — use `:deep(div)` for gradient styles112- CSS `.reveal` + `.text-reveal` pre-hide classes prevent FOUC113114---115116## 5. useReveal Composable Reference117118| Method | Description |119|--------|-------------|120| `init(fn)` | Async: waits for fonts, lazy-loads SplitText + Scramble, creates gsap.context |121| `hero(selector?, overrides?)` | Immediate reveal for above-fold. Auto-detects `.text-reveal` children |122| `scroll(selector?, overrides?)` | Per-element ScrollTrigger reveal with `.reveal` + `.text-reveal` |123| `split(target)` | SplitText wrapper: creates masked word split, tracks for cleanup |124125```js126// Hero (above-fold, immediate)127const { init, hero } = useReveal(sectionRef)128onMounted(() => init(() => hero('.reveal', { textDelay: 0.2 })))129130// Scroll (below-fold, per-element ScrollTrigger)131const { init, scroll } = useReveal(sectionRef)132onMounted(() => init(() => scroll('.reveal', { start: 'top 75%', once: true })))133```134135Cleanup is automatic — `onUnmounted` reverts gsap.context and all SplitText instances.136137---138139## References140141- `references/text-patterns.md` — Combined clip+scramble, kinetic character split, elastic type assembly, rolling text (3D slot machine), horizontal text (containerAnimation), masked lines reveal, autoSplit with ScrollTrigger142- `references/learnings.md` — 7 critical gotchas from production debugging