Vanilla Examples
Common animation patterns using the splitText function with vanilla JavaScript.
Basic Fade In
Split text into words and animate each one with a staggered fade and slide effect.
Fade in each word
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
const { words } = splitText(element);
animate(
words,
{ opacity: [0, 1], y: [20, 0] },
{ delay: stagger(0.05), duration: 0.5 }
);Character Reveal
Animate each character individually for a more granular effect. Great for headings and short text.
Character by character
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
const { chars } = splitText(element);
animate(
chars,
{ opacity: [0, 1], scale: [0.5, 1] },
{ delay: stagger(0.02), duration: 0.3 }
);Line by Line
Animate each line separately. Lines are detected based on actual rendered positions, so this works with any text that wraps naturally.
This paragraph animates line by line. Each line slides in from the left.
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
const { lines } = splitText(element);
animate(
lines,
{ opacity: [0, 1], x: [-20, 0] },
{ delay: stagger(0.1), duration: 0.6 }
);Nested Elements
Fetta preserves inline HTML elements like <a>, <em>, and <strong> when splitting text. All attributes (href, class, id, data-*, etc.) are maintained on the split output.
Click this link or see emphasized and bold text
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
// HTML: Click <a href="#">this link</a> or see <em>emphasized</em> text
const { chars } = splitText(element);
animate(
chars,
{ opacity: [0, 1], y: [10, 0] },
{ delay: stagger(0.02), duration: 0.3 }
);Masked Line Reveal
Use the mask option to wrap elements in a clipping container with overflow: clip. This creates clean reveal animations where content slides into view from outside its bounds.
Each line reveals from below with a clipping mask for a clean effect.
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
splitText(element, {
type: 'lines',
mask: 'lines',
onSplit: ({ lines }) => {
animate(
lines,
{ y: ['100%', '0%'] },
{ delay: stagger(0.1), duration: 0.5 }
);
}
});With Auto-Revert
Use onSplit with revertOnComplete: true to automatically restore the original HTML when the animation finishes. Return your animation from the callback - Fetta handles extracting the .finished promise.
Auto-revert after animation
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
splitText(element, {
onSplit: ({ chars }) =>
animate(chars, { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.02), duration: 0.3 }),
revertOnComplete: true
});Responsive Split
Enable autoSplit to automatically re-split text when the container resizes. Use onResize to re-trigger animations when the text reflows to different lines.
If you only want to animate once on load, skip onResize and trigger your animation externally. Fetta will still re-split the text on resize to maintain correct line detection, but won't call any callback.
This text reflows naturally at any width, animating again when lines change.
import { splitText } from 'fetta';
import { animate, stagger } from 'motion';
const element = document.querySelector('p');
const result = splitText(element, {
autoSplit: true,
onResize: ({ lines }) => {
animate(
lines,
{ opacity: [0, 1], y: [12, 0] },
{ delay: stagger(0.08), duration: 0.4 }
);
}
});
// Cleanup when done (optional - auto-cleans when element removed from DOM)
result.revert();Scroll-Triggered
Use Motion's inView to detect when the element enters the viewport and trigger the animation. Return a cleanup function to reset styles when the element leaves view.
Reveals on scroll
import { splitText } from 'fetta';
import { animate, stagger, inView } from 'motion';
const element = document.querySelector('p');
const { words } = splitText(element);
// Hide words initially
words.forEach(w => {
w.style.opacity = '0';
w.style.transform = 'translateY(30px)';
});
// Trigger animation when element enters viewport
inView(element, () => {
animate(words, { opacity: [0, 1], y: [30, 0] }, { delay: stagger(0.03) });
// Reset styles when leaving view
return () => {
words.forEach(w => {
w.style.opacity = '0';
w.style.transform = 'translateY(30px)';
});
};
}, { amount: 0.5 });Scroll-Driven
Link animations to scroll progress using Motion's scroll() function. The animation plays as the target element moves through the viewport.
Each word reveals as you scroll through this container
import { splitText } from 'fetta';
import { animate, scroll } from 'motion';
const target = document.querySelector('.text-container');
const element = target.querySelector('p');
const { words } = splitText(element);
const animation = animate(
words.map((word, i) => [
word,
{ opacity: [0, 1], y: [20, 0] },
{ duration: 0.5, at: i * 0.1, ease: 'linear' }
])
);
scroll(animation, { target, offset: ['start 85%', 'start 20%'] });With GSAP
Fetta works with any animation library. Here's an example using GSAP.
Animated with GSAP
import { splitText } from 'fetta';
import gsap from 'gsap';
const element = document.querySelector('p');
const { words } = splitText(element);
gsap.from(words, {
opacity: 0,
y: 30,
stagger: 0.05,
duration: 0.5,
ease: "power2.out"
});CSS-Only Animation
Use propIndex to add CSS variables (--char-index, --word-index, --line-index) to each element. Animate entirely with CSS — no JavaScript animation library needed.
Pure CSS animation
import { splitText } from 'fetta';
const element = document.querySelector('p');
splitText(element, { propIndex: true });.split-char {
opacity: 0;
transform: translateY(20px);
animation: fade-in 0.4s ease forwards;
animation-delay: calc(var(--char-index) * 0.03s);
}
@keyframes fade-in {
to {
opacity: 1;
transform: translateY(0);
}
}