Text Scramble

On hover, each character flickers through random glyphs and locks into place from left to right, like a display decoding.

  • JS
  • Text
  • Hover
  • Added

AI prompt

Write a dependency-free React text scramble that plays on hover and keyboard focus. Keep the target string, and on start run a requestAnimationFrame loop for about 700ms. Each frame, characters before the progress index show their final letter, and the rest show a random glyph from the pool "ABCDEFGHIJKLMNOPQRSTUVWXYZ#%&*+=/<>". Keep spaces as spaces. Cancel the loop on unmount and when a new run starts. Use a monospace font so the width does not jump, and put the real text in aria-label so screen readers never hear the noise. Under prefers-reduced-motion, show the final text without scrambling. The text reads "Decode the signal".

Source

Demo.tsx
'use client'

import { useEffect, useRef, useState } from 'react'
import s from './demo.module.css'

const DURATION = 700

export default function TextScramble({ labels }: { labels: Record<string, string> }) {
  const target = labels.text
  const pool = Array.from(labels.glyphs)
  const [shown, setShown] = useState(target)
  const frame = useRef(0)

  useEffect(() => () => cancelAnimationFrame(frame.current), [])

  const run = () => {
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
    cancelAnimationFrame(frame.current)
    const chars = Array.from(target)
    const start = performance.now()
    const tick = (now: number) => {
      const progress = Math.min((now - start) / DURATION, 1)
      const locked = Math.floor(progress * chars.length)
      setShown(
        chars
          .map((c, i) => (i < locked || c === ' ' ? c : pool[Math.floor(Math.random() * pool.length)]))
          .join(''),
      )
      if (progress < 1) frame.current = requestAnimationFrame(tick)
    }
    frame.current = requestAnimationFrame(tick)
  }

  return (
    <span className={s.text} tabIndex={0} aria-label={target} onPointerEnter={run} onFocus={run}>
      <span aria-hidden="true">{shown}</span>
    </span>
  )
}
demo.module.css
.text {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  font: 600 var(--text-2xl) / 1.2 var(--font-mono);
  letter-spacing: -0.01em;
  color: var(--stage-ink);
  white-space: nowrap;
  cursor: default;
}

.text:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.