Glitch

kinetic

Red and cyan ghosts jitter apart before snapping back together.

I Love Bangladesh

17/120
Align
Ease
Duration
s
0.13
Stagger
s
00.2
Delay
s
02
export function glitch(el) {
  const _reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
  const _duration = _reducedMotion ? 0.01 : 0.6
  const _delay = _reducedMotion ? 0 : 0

  const text = el.textContent ?? ''
  el.setAttribute('aria-label', text)
  el.style.position = 'relative'
  const clones = [0, 1].map((i) => {
    const clone = document.createElement('span')
    clone.textContent = text
    clone.className = i ? 'k-glitch-cyan' : 'k-glitch-red'
    clone.setAttribute('aria-hidden', 'true')
    el.append(clone)
    return clone
  })
  const easing = 'steps(6)'
  const legMs = (_duration / 6) * 1000
  const delayMs = _delay * 1000
  // 6 alternating legs, matching GSAP's repeat: 5, yoyo: true — an even leg
  // count lands back on the "from" keyframe, so both clones settle neutral
  // without needing an explicit reset tween at the end.
  const anim0 = clones[0].animate(
    [
      { transform: 'translateX(0px)', clipPath: 'inset(0% 0 0% 0)' },
      { transform: 'translateX(-6px)', clipPath: 'inset(10% 0 40% 0)' },
    ],
    { duration: legMs, delay: delayMs, iterations: 6, direction: 'alternate', easing, fill: 'both' },
  )
  const anim1 = clones[1].animate(
    [
      { transform: 'translateX(0px)', clipPath: 'inset(0% 0 0% 0)' },
      { transform: 'translateX(6px)', clipPath: 'inset(40% 0 10% 0)' },
    ],
    { duration: legMs, delay: delayMs, iterations: 6, direction: 'alternate', easing, fill: 'both' },
  )
  return () => {
    anim0.cancel()
    anim1.cancel()
    clones.forEach((c) => c.remove())
  }
}

/* CSS */
.k-glitch-red,
.k-glitch-cyan {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0;
  pointer-events: none;
}

.k-glitch-red {
  color: #ff004c;
  mix-blend-mode: screen;
}

.k-glitch-cyan {
  color: #00e5ff;
  mix-blend-mode: screen;
}