Char Scramble

hover

Hover or focus to scramble the text before it decodes back.

Hover the text
17/120
Align
Ease
Duration
s
0.13
Stagger
s
00.2
Delay
s
02
export function charScramble(el) {
  const _reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
  const _duration = _reducedMotion ? 0.35 : 0.5

  const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  const randChar = () => CHARS[Math.floor(Math.random() * CHARS.length)]
  const text = el.textContent ?? ''
  const chars = [...text]
  const points = [0, 1]
  const revealDelay = 0.2
  const durationMs = _duration * 1000
  let raf = 0
  let start = null
  let running = false

  const tick = (now) => {
    if (!running) return
    if (start === null) start = now
    const elapsed = now - start
    const t = Math.min(1, elapsed / durationMs)
    const progress = easeAt(points, t)
    const revealT = Math.max(0, (progress - revealDelay) / (1 - revealDelay))
    const revealCount = Math.floor(revealT * chars.length)
    el.textContent = chars.map((c, i) => (i < revealCount || c === ' ' ? c : randChar())).join('')
    if (t < 1) {
      raf = requestAnimationFrame(tick)
    } else {
      el.textContent = text
      running = false
    }
  }
  const enter = () => {
    cancelAnimationFrame(raf)
    running = true
    start = null
    raf = requestAnimationFrame(tick)
  }
  const leave = () => {
    running = false
    cancelAnimationFrame(raf)
    el.textContent = text
  }
  const ons = [['pointerenter', enter], ['pointerleave', leave], ['focus', enter], ['blur', leave]]
  ons.forEach(([e, fn]) => el.addEventListener(e, fn))
  return () => {
    running = false
    cancelAnimationFrame(raf)
    ons.forEach(([e, fn]) => el.removeEventListener(e, fn))
    el.textContent = text
  }
}

function easeAt(points, t) {
  const clamped = t < 0 ? 0 : t > 1 ? 1 : t
  const scaled = clamped * (points.length - 1)
  const i = Math.floor(scaled)
  const frac = scaled - i
  const a = points[i]
  const b = points[Math.min(i + 1, points.length - 1)]
  return a + (b - a) * frac
}