22 CSS Transition Effects 21 / 22

Split Text Reveal Transition

Character-split hero headline, word-by-word reveal, rotating word swap, per-character colour wave and text scramble with progressive decode.

CSS + JS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<div class="page">
  <h1>Split Text Reveal Transition</h1>
  <p class="subtitle">Characters, words and lines animated into view</p>

  <!-- 1. Hero -->
  <section>
    <h2>Hero Headline — Character Split</h2>
    <button class="replay-btn" onclick="runAll()">↺ Replay All</button>
    <div class="hero">
      <div class="big split-text from-below" id="heroL1"><div class="line"></div></div>
      <div class="big split-text from-below" id="heroL2"><div class="line"></div></div>
      <div class="sub split-text from-below" id="heroSub"><div class="line"></div></div>
    </div>
  </section>

  <!-- 2. Word reveal -->
  <section>
    <h2>Word-by-Word Reveal</h2>
    <div class="word-reveal" id="wordReveal"></div>
  </section>

  <!-- 3. Rotating swap -->
  <section>
    <h2>Rotating Word Swap</h2>
    <div class="swap-container">
      <span class="swap-static">We build</span>
      <div class="swap-word" id="swapWord"><span class="active">interfaces</span></div>
    </div>
  </section>

  <!-- 4. Wave -->
  <section>
    <h2>Character Colour Wave</h2>
    <div class="wave-text" id="waveText"></div>
  </section>

  <!-- 5. Scramble -->
  <section>
    <h2>Text Scramble Reveal</h2>
    <div class="scramble-block" id="scrambleBlock"></div>
  </section>
</div>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #0c0c0e; min-height: 100vh; overflow-x: hidden; }

.page { padding: 70px 40px; max-width: 1000px; margin: 0 auto; }
h1 { color: #fff; text-align: center; font-size: 2rem; margin-bottom: 8px; }
.subtitle { color: rgba(255,255,255,.4); text-align: center; margin-bottom: 60px; }
section { margin-bottom: 80px; }
section > h2 { color: rgba(255,255,255,.3); font-size: .72rem; text-transform: uppercase; letter-spacing: 3px; margin-bottom: 30px; }
.replay-btn {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 24px;
  padding: 8px 18px; border-radius: 8px; border: 1px solid rgba(255,255,255,.15);
  background: rgba(255,255,255,.05); color: rgba(255,255,255,.7); font-size: .82rem; cursor: pointer;
  transition: background .2s;
}
.replay-btn:hover { background: rgba(255,255,255,.1); }

/* ─────────────────────────────────────
   Shared split char helpers
   Each .char is a clip container; the inner span slides in from below / above
───────────────────────────────────── */
.split-text { overflow: hidden; display: inline-block; }
.split-text .line { overflow: hidden; display: block; }
.split-text .char {
  display: inline-block; overflow: hidden;
  vertical-align: top; /* prevent layout shift */
}
.split-text .char .inner {
  display: inline-block;
  transition: transform .6s cubic-bezier(.16,1,.3,1), opacity .6s ease;
}

/* Initial hidden states */
.from-below  .char .inner { transform: translateY(110%); opacity: 0; }
.from-above  .char .inner { transform: translateY(-110%); opacity: 0; }
.from-left   .char .inner { transform: translateX(-80px); opacity: 0; }
.from-right  .char .inner { transform: translateX(80px); opacity: 0; }
.scale-blur  .char .inner { transform: scale(1.4); opacity: 0; filter: blur(6px); transition: transform .7s cubic-bezier(.16,1,.3,1), opacity .6s, filter .6s; }
.rotate-in   .char .inner { transform: rotateX(90deg) translateY(40px); opacity: 0; transform-origin: top center; transition: transform .65s cubic-bezier(.16,1,.3,1), opacity .5s; }

/* Revealed state */
.char.visible .inner { transform: none; opacity: 1; filter: none; }

/* ─────────────────────────────────────
   1. Hero headline — from-below
───────────────────────────────────── */
.hero { text-align: center; padding: 20px 0; }
.hero .big { font-size: clamp(2.5rem, 7vw, 5rem); font-weight: 900; line-height: 1.05; letter-spacing: -.02em; }
.hero .line1 { color: #fff; }
.hero .line2 {
  background: linear-gradient(90deg,#a78bfa,#60a5fa);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.hero .sub {
  margin-top: 20px; color: rgba(255,255,255,.5); font-size: 1.1rem; overflow: hidden;
}

/* ─────────────────────────────────────
   2. Staggered word reveal
───────────────────────────────────── */
.word-reveal { font-size: 1.6rem; font-weight: 700; color: #fff; line-height: 1.5; }
.word-reveal .word { display: inline-block; overflow: hidden; margin-right: .25em; }
.word-reveal .word .inner {
  display: inline-block;
  transform: translateY(100%);
  transition: transform .55s cubic-bezier(.16,1,.3,1);
}
.word-reveal .word.visible .inner { transform: none; }

/* ─────────────────────────────────────
   3. Rotating word swap
───────────────────────────────────── */
.swap-container { display: flex; align-items: baseline; gap: 12px; font-size: 2rem; font-weight: 800; }
.swap-static { color: #fff; }
.swap-word {
  color: #a78bfa; display: inline-block; overflow: hidden;
  min-width: 200px;
}
.swap-word .active {
  display: block; animation: swap-in .5s cubic-bezier(.16,1,.3,1) forwards;
}
.swap-word .exit {
  display: block; animation: swap-out .4s ease-in forwards;
}
@keyframes swap-in  { from { transform: translateY(100%); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes swap-out { from { transform: none; opacity: 1; } to { transform: translateY(-100%); opacity: 0; } }

/* ─────────────────────────────────────
   4. Per-character colour wave
───────────────────────────────────── */
.wave-text {
  font-size: 3rem; font-weight: 900; letter-spacing: -.02em;
  display: flex; flex-wrap: wrap; gap: 0;
}
.wave-text .char {
  display: inline-block; overflow: hidden;
  color: rgba(255,255,255,.15);
  transition: color .4s ease;
}
.wave-text .char.lit { color: #fff; }
.wave-text .char.space { width: .35em; }

/* ─────────────────────────────────────
   5. Scramble + reveal (JS)
───────────────────────────────────── */
.scramble-block { display: flex; flex-direction: column; gap: 10px; }
.scramble-item { overflow: hidden; }
.scramble-target { font-size: 1.2rem; font-weight: 600; color: #fff; font-family: monospace; letter-spacing: .02em; }
.scramble-target .reveal-bar {
  position: relative; display: inline-block;
  background: linear-gradient(90deg,#7c3aed,#3b82f6); border-radius: 2px;
  height: 3px; width: 0; display: block; margin-top: 4px;
  transition: width 1s ease;
}
.scramble-target.done .reveal-bar { width: 100%; }
/* ── 1. Build char splits ─────────────────────────── */
function buildCharSplit(el, text, baseDelay = 0) {
  const line = el.querySelector('.line');
  line.innerHTML = '';
  [...text].forEach((ch, i) => {
    const wrap = document.createElement('span');
    wrap.className = 'char';
    if (ch === ' ') { wrap.style.width = '.3em'; wrap.style.display = 'inline-block'; }
    const inner = document.createElement('span');
    inner.className = 'inner';
    inner.textContent = ch;
    wrap.appendChild(inner);
    line.appendChild(wrap);
    // stagger
    setTimeout(() => wrap.classList.add('visible'), baseDelay + i * 40);
  });
}

function resetSplit(el) {
  el.querySelectorAll('.char').forEach(c => c.classList.remove('visible'));
}

/* ── 2. Word reveal ───────────────────────────────── */
const WORDS = 'Design systems that feel like magic.'.split(' ');
function buildWordReveal() {
  const el = document.getElementById('wordReveal');
  el.innerHTML = '';
  WORDS.forEach((w, i) => {
    const wrap = document.createElement('span');
    wrap.className = 'word';
    const inner = document.createElement('span');
    inner.className = 'inner';
    inner.textContent = w;
    wrap.appendChild(inner);
    el.appendChild(wrap);
    el.appendChild(document.createTextNode(' '));
    setTimeout(() => wrap.classList.add('visible'), 300 + i * 90);
  });
}

/* ── 3. Rotating swap ────────────────────────────── */
const SWAP_WORDS = ['interfaces','experiences','products','identities','systems','futures'];
let swapIdx = 0;
function nextSwap() {
  const el = document.getElementById('swapWord');
  const active = el.querySelector('.active');
  swapIdx = (swapIdx + 1) % SWAP_WORDS.length;
  if (active) {
    active.className = 'exit';
    setTimeout(() => active.remove(), 420);
  }
  const next = document.createElement('span');
  next.className = 'active';
  next.textContent = SWAP_WORDS[swapIdx];
  el.appendChild(next);
}
setInterval(nextSwap, 2200);

/* ── 4. Wave ─────────────────────────────────────── */
const WAVE_TEXT = 'CREATIVE MOTION';
function buildWave() {
  const el = document.getElementById('waveText');
  el.innerHTML = '';
  [...WAVE_TEXT].forEach((ch, i) => {
    const span = document.createElement('span');
    span.className = 'char' + (ch === ' ' ? ' space' : '');
    span.textContent = ch;
    el.appendChild(span);
  });
}
function runWave(delay = 0) {
  const chars = [...document.querySelectorAll('#waveText .char:not(.space)')];
  chars.forEach(c => c.classList.remove('lit'));
  chars.forEach((c, i) => setTimeout(() => c.classList.add('lit'), delay + i * 55));
  // Loop
  setTimeout(() => {
    chars.forEach(c => c.classList.remove('lit'));
    setTimeout(() => runWave(0), 400);
  }, delay + chars.length * 55 + 800);
}

/* ── 5. Scramble ──────────────────────────────────── */
const LINES = ['The quick brown fox','Jumps over the lazy','Dog at dawn'];
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%';
function scramble(el, target, delay = 0) {
  const bar = el.querySelector('.reveal-bar');
  const text = el.querySelector('.text-content') || el;
  let frame = 0;
  const total = 30;
  setTimeout(() => {
    const iv = setInterval(() => {
      const progress = frame / total;
      const revealedCount = Math.floor(progress * target.length);
      let out = '';
      for (let i = 0; i < target.length; i++) {
        if (target[i] === ' ') { out += ' '; continue; }
        if (i < revealedCount) { out += target[i]; }
        else { out += CHARS[Math.floor(Math.random() * CHARS.length)]; }
      }
      el.childNodes[0].textContent = out;
      frame++;
      if (frame > total) {
        clearInterval(iv);
        el.childNodes[0].textContent = target;
        el.classList.add('done');
      }
    }, 35);
  }, delay);
}

function buildScramble() {
  const block = document.getElementById('scrambleBlock');
  block.innerHTML = '';
  LINES.forEach((line, i) => {
    const item = document.createElement('div');
    item.className = 'scramble-item';
    const tgt = document.createElement('div');
    tgt.className = 'scramble-target';
    tgt.appendChild(document.createTextNode(line));
    const bar = document.createElement('span');
    bar.className = 'reveal-bar';
    tgt.appendChild(bar);
    item.appendChild(tgt);
    block.appendChild(item);
    scramble(tgt, line, 500 + i * 600);
  });
}

/* ── Init & replay ───────────────────────────────── */
function heroLine1() {
  const el = document.getElementById('heroL1');
  el.classList.add('line1');
  buildCharSplit(el, 'Build Beautiful', 0);
}
function heroLine2() {
  const el = document.getElementById('heroL2');
  el.classList.add('line2');
  buildCharSplit(el, 'Transitions', 200);
}
function heroSub() {
  buildCharSplit(document.getElementById('heroSub'), 'Every character, perfectly timed.', 600);
}

function runAll() {
  heroLine1(); heroLine2(); heroSub();
  buildWordReveal();
  buildScramble();
}

buildWave();
runAll();
runWave(1200);

Search CodeFronts

Loading…