Split Text Animation — Free HTML CSS JS Snippet

Split Text Animation · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Word reveal: overflow:hidden + translateY(110%) + staggered 90ms delay per word
Character slide-up: per-char 40ms delay, rotate(8deg)→0 snap on landing
Line wipe: single translateY(100%)→0 inside overflow:hidden container
Scramble decode: requestAnimationFrame progress-based character locking
Replay button: resets all innerHTML and re-runs all four animations
Dark #0f172a background — split text effects read best on dark surfaces
Spring cubic-bezier(0.16,1,0.3,1) easing matches premium motion design feel
Four independent variants — use any combination in your project

About this UI Snippet

Split Text Animation — Word Reveal, Character Slide-Up, Line Wipe & Scramble Decode

Screenshot of the Split Text Animation snippet rendered live

Split text animations are one of the most in-demand effects on modern landing pages and creative portfolios. They make large display headings (especially gradient text) feel dynamic and intentional — the text appears to emerge from below, one word or character at a time, creating a cinematic reveal that draws the eye and communicates design quality. This snippet provides four distinct split text animation variants: word-by-word reveal, character slide-up with rotation, single-line wipe, and a scramble decode effect — all in plain HTML, CSS, and vanilla JavaScript.

Word-by-word reveal

The text is split into words. Each word is wrapped in two spans: an outer .word-wrap with overflow: hidden, and an inner .word that starts at translateY(110%) — completely below the clip boundary. A CSS @keyframes animation moves each word to translateY(0). Staggered animation-delay (0, 0.09s, 0.18s...) per word creates the sequential reveal. The overflow: hidden on the wrapper makes the word appear to "wipe up" from below rather than fade in.

Character slide-up with rotation

Identical architecture to word reveal but split by character. Each character starts at translateY(110%) rotate(8deg) and animates to translateY(0) rotate(0). The 8° starting rotation adds a slight snap as each character lands upright. The stagger is 40ms per character — tight enough to feel unified, loose enough to see each character individually.

Line wipe reveal

The entire line starts at translateY(100%) inside an overflow: hidden container. A single animation moves the line from below the container to its natural position. No splitting needed — the overflow clip creates the wipe-up effect at the full line level. This is the fastest and most impactful variant for short display text.

Scramble decode

A requestAnimationFrame loop runs for 1200ms. Progress (0 to 1) determines how many characters have been "revealed" to their final value. Unrevealed characters show random characters from a 70-char pool each frame, creating the scramble effect. As progress increases, more characters lock into their final positions from left to right.

Combining multiple variants for a sequence

Chain all four variants with increasing base delays for a coordinated reveal sequence: splitWords(headline, 0); splitChars(subheading, 0.6); wipeReveal(tagline, 1.0); scramble(cta, 1.4). The scramble variant is the same effect as the standalone text scramble. The delays create a natural reading order — headline first, supporting text follows. Use this pattern for full-screen hero sections like the word-flip hero where each text element appears sequentially.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't need to work out why four different reveal techniques all lean on the same clip trick by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why translateY(110%) inside an overflow hidden wrapper reads as a "wipe up" rather than a fade, or how the scramble function's requestAnimationFrame loop decides how many characters have locked in versus how many are still randomized based on elapsed time. The same assistant can help optimize it, for example checking whether the character-split variant's 40ms-per-character stagger becomes uncomfortably slow for long strings and whether it should switch to word-splitting past some length. It's also useful for extending the feature: ask it to trigger each variant via IntersectionObserver instead of only on page load or button click, respect prefers-reduced-motion by skipping straight to the final text, or add a fifth variant like a blur-to-sharp reveal. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build four distinct text reveal animations in plain HTML, CSS, and JavaScript, no framework, no libraries: a word-by-word reveal, a character slide-up, a single-line wipe, and a scramble decode.

Requirements:
- For the word reveal, split an element's text content on whitespace into individual words, wrap each word in an outer span with overflow hidden and an inner span that starts transformed fully below the wrapper (translateY well over 100 percent), then animate the inner span to translateY(0) via a CSS keyframe, staggering each word's animation-delay by a fixed increment based on its index.
- For the character slide-up, do the same clip-and-slide technique per character instead of per word, but also start each character rotated a few degrees and animate both the rotation and the vertical position back to neutral together, using a shorter per-character stagger increment than the per-word one.
- For the line wipe, do not split the text at all — wrap the whole line in a single overflow hidden container and animate the entire line from translateY(100%) to 0 as one unit.
- For the scramble decode, run a requestAnimationFrame loop for a fixed duration that computes an elapsed-time-based progress fraction, reveals characters from the start of the string up to a count derived from that progress, and fills all not-yet-revealed character positions with a random character from a large character pool on every single frame (leaving actual space characters as spaces, never randomized).
- Provide a single function that resets and restarts all four animations together with staggered base delays so they play in a coordinated sequence rather than simultaneously, and make sure any in-flight scramble animation frame is properly cancelled before a restart.
- As a documented accessibility improvement in a code comment, show how checking prefers-reduced-motion would skip directly to each element's final text with no animation at all.

Step by step

How to Use

  1. 1
    Click "↺ Replay all" to see all four animations restartThe word reveal, character slide-up, line wipe, and scramble decode all run in sequence with staggered starts. Each animation has a different timing — observe how each creates a different visual impression.
  2. 2
    Use the word reveal for hero headlinesCall splitWords(el, baseDelay) where el is your headline element. The function splits text into words, wraps each in overflow:hidden spans, and applies staggered CSS animation delays. Works for any text length.
  3. 3
    Use character split for short impact textCall splitChars(el, baseDelay) for 2-4 word text. Best for short, punchy text — "Bold. Fast. Free." The character rotation adds energy. Avoid for long text where 40ms×40 chars = 1.6s total before the last character lands.
  4. 4
    Trigger animations on scroll with IntersectionObserverWrap the replayAll() call in an IntersectionObserver: const obs = new IntersectionObserver(([e]) => { if(e.isIntersecting) { replayAll(); obs.disconnect(); } }, { threshold: 0.3 }); obs.observe(document.querySelector(".stage")).
  5. 5
    Change animation timing and easingUpdate cubic-bezier(0.16,1,0.3,1) in the @keyframes CSS for different feels. This is a spring-style ease-out. Use ease for softer, linear for mechanical. Change 0.6s duration to 0.4s for snappier or 1s for more dramatic reveals.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using useEffect to trigger on mount or scroll, or "Tailwind" for a React + Tailwind CSS version.

Real-world uses

Common Use Cases

Hero section headline animations on landing pages
The word-by-word reveal is the dominant text animation on premium SaaS landing pages. Large display headlines (48-80px) with word splits feel cinematic and signal design investment. Trigger on page load for above-the-fold text, on scroll for below-the-fold sections.
Portfolio and agency site dramatic text reveals
Creative agencies and designers use split text animations to make their own sites feel as polished as their client work. The character slide-up with rotation creates the highest visual drama for 2-3 word impact statements.
Product launch countdown and announcement pages
For coming-soon and launch pages, the scramble decode creates tension and excitement — text appears to resolve from chaos to order. Pair it with a countdown timer for a dramatic pre-launch page.
Onboarding flow step headlines with scroll triggers
Each onboarding step has a headline that reveals as the step becomes visible. The line wipe variant is fastest to implement and most subtle — appropriate for functional UI rather than pure marketing pages.
Study overflow:hidden clip and CSS animation stagger techniques
The word and character reveal patterns both use the same overflow:hidden + translateY trick. The stagger uses CSS animation-delay computed from index. These two techniques — clip boundary + staggered delay — are the foundation of most premium text animation implementations.
Replace GSAP SplitText for simple text reveals
GSAP SplitText is a premium paid plugin. This snippet covers the most common use cases (word split, character split, line wipe) in under 80 lines of vanilla JavaScript. For projects that only need simple text reveals, this zero-dependency implementation has identical visual output.

Got questions?

Frequently Asked Questions

The .word-wrap (or .line-wipe-wrap for the line variant) has overflow: hidden. Inside it, the text element starts at transform: translateY(110%) — 110% of the element height, positioning it completely below the container's bottom edge. Because overflow is hidden, the text is invisible. As translateY animates to 0, the text rises from below the invisible clip boundary into view. The result looks like the text is wiping up from the baseline — a much more polished effect than a simple fade-in.

Use IntersectionObserver: const elements = document.querySelectorAll(".split-words, .split-chars"); const obs = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { if (entry.target.classList.contains("split-words")) splitWords(entry.target); else splitChars(entry.target); obs.unobserve(entry.target); } }); }, { threshold: 0.3 }); elements.forEach(el => obs.observe(el)). The obs.unobserve() prevents re-animating on scroll back.

Add: if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { document.getElementById("split-words").textContent = "Design with intention"; return; } at the top of replayAll(). For CSS: @media (prefers-reduced-motion: reduce) { .word, .char { animation: none; transform: none; opacity: 1; } .line-wipe { animation: none; transform: none; } }. This makes all text immediately visible without animation for users who have enabled the system accessibility setting.

Click "JSX" to download. Run the split functions in a useEffect on mount: useEffect(() => { splitWords(wordsRef.current, 0); splitChars(charsRef.current, 0.4); ... }, []). Use useRef for each animation target element. For scroll-triggered animations, add an IntersectionObserver inside the useEffect. Return a cleanup: return () => cancelAnimationFrame(scrambleFrame) to cancel any ongoing rAF loop when the component unmounts.