25 CSS Text Animations 18 / 25

CSS Word Scramble Animation

Characters rapidly cycle through random glyphs before locking into the final letter — a hacker/terminal decryption effect powered by JavaScript.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ta-18">
  <div class="ta-18__stage">
    <p class="ta-18__label">Decrypting</p>
    <h2 class="ta-18__text" id="ta-18-text" aria-label="ACCESS GRANTED"></h2>
    <p class="ta-18__sub">JS character cycling · random glyph pool · staggered resolve</p>
  </div>
</div>
.ta-18, .ta-18 *, .ta-18 *::before, .ta-18 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.ta-18 ::selection { background: #065f46; color: #d1fae5; }

.ta-18 {
  --bg: #030d06;
  --green: #4ade80;
  --dim: #166534;
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  font-family: 'JetBrains Mono', 'Courier New', monospace;
}

.ta-18__stage { text-align: center; }

.ta-18__label {
  font-size: 0.68rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 0.5rem;
}

.ta-18__text {
  font-size: clamp(1.8rem, 5.5vw, 3.2rem);
  font-weight: 700;
  letter-spacing: 0.1em;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.05em;
  min-height: 1.2em;
}

.ta-18__char {
  display: inline-block;
  color: var(--dim);
  transition: color 0.1s;
}

.ta-18__char.resolved {
  color: var(--green);
  text-shadow: 0 0 8px rgba(74, 222, 128, 0.5);
}

.ta-18__sub {
  font-size: 0.65rem;
  color: #052010;
  margin-top: 0.8rem;
  letter-spacing: 0.08em;
}

@media (prefers-reduced-motion: reduce) {
  .ta-18__char { transition: none; }
}
(function() {
  const el = document.getElementById('ta-18-text');
  if (!el) return;

  const finalText = 'ACCESS GRANTED';
  const chars = '!@#$%^&*<>?/|\\[]{}ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const resolveDelay = 6;

  function scramble() {
    el.innerHTML = '';
    const spans = [];
    [...finalText].forEach((ch) => {
      const span = document.createElement('span');
      span.className = 'ta-18__char';
      span.textContent = ch === ' ' ? ' ' : chars[Math.floor(Math.random() * chars.length)];
      el.appendChild(span);
      spans.push({ el: span, final: ch, cycles: 0, resolved: false });
    });

    let tick = 0;
    const id = setInterval(() => {
      tick++;
      let allDone = true;
      spans.forEach((s, i) => {
        if (s.resolved) return;
        if (tick >= resolveDelay + i * 3) {
          s.el.textContent = s.final === ' ' ? ' ' : s.final;
          s.el.classList.add('resolved');
          s.resolved = true;
        } else {
          s.el.textContent = s.final === ' ' ? ' ' : chars[Math.floor(Math.random() * chars.length)];
          allDone = false;
        }
      });
      if (allDone) {
        clearInterval(id);
        setTimeout(scramble, 2200);
      }
    }, 65);
  }

  scramble();
})();

How this works

JavaScript splits the target word into individual characters and uses setInterval to rapidly replace each character's displayed glyph with a random character from a pool of symbols, digits, and letters. The interval fires every 50–80ms, creating a flickering scramble. Simultaneously, a counter tracks how many 'resolve' cycles each character has completed before locking it to its final value.

Characters resolve left-to-right: position 0 locks first after N cycles, position 1 locks after N+k cycles, and so on. The CSS styles each character span with a monospace font and a distinct colour — scrambling characters shown in a secondary accent colour while resolved characters display in the final primary colour, providing clear visual feedback about the decode progress.

Customize

  • Change the character pool in the chars string — use only digits for a numeric ticker, only symbols for a sci-fi alien script, or Greek letters for a scholarly look.
  • Adjust scramble speed by changing the setInterval delay from 60ms — faster (30ms) for a frantic glitch, slower (120ms) for a deliberate decode.
  • Change the stagger rate by adjusting the resolveAfter calculation per character — a smaller multiplier resolves all characters almost simultaneously.
  • Trigger on hover using a mouseenter event listener that starts the scramble, combined with a mouseleave that resets to the original text.
  • Chain multiple elements by starting the next word's scramble when the previous resolves, creating a cascading reveal of a headline, subheading, and body line.

Watch out for

  • Always clear the interval with clearInterval when all characters have resolved — leaking intervals cause the effect to restart unexpectedly and waste CPU.
  • Screen readers will read out every scrambled character update as it changes — always add aria-live="off" and aria-label with the final text on the container.
  • Rapidly updating DOM text nodes causes reflows if the element width changes character-by-character — use a monospaced font and set a fixed min-width on the container equal to the final word width.

Browser support

ChromeSafariFirefoxEdge
All All All All

Pure DOM manipulation with setInterval — works in every browser that supports JavaScript. Ensure aria-label provides the final readable text.

Search CodeFronts

Loading…