Exit Scatter

exit

Characters scatter outward in every direction and vanish.

I Love Bangladesh

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

  const split = splitChars(el)
  const easing = 'linear(0.0000,0.0000,0.0001,0.0005,0.0011,0.0021,0.0036,0.0058,0.0086,0.0123,0.0169,0.0224,0.0291,0.0370,0.0463,0.0569,0.0691,0.0828,0.0983,0.1156,0.1349,0.1561,0.1795,0.2051,0.2330,0.2634,0.2963,0.3318,0.3701,0.4111,0.4552,0.5022,0.5524,0.6058,0.6626,0.7228,0.7865,0.8539,0.9250,1.0000)'
  const anims = split.chars.map((c, i) => {
    const angle = (i / split.chars.length) * Math.PI * 2
    const x = Math.cos(angle) * 60
    const y = Math.sin(angle) * 60
    const rotation = (i % 2 ? 1 : -1) * 45
    return c.animate(
      [
        { transform: 'none', opacity: 1 },
        { transform: `translate(${x}px, ${y}px) rotate(${rotation}deg)`, opacity: 0 },
      ],
      {
        duration: _duration * 1000,
        delay: (_delay + i * _stagger) * 1000,
        easing,
        fill: 'forwards',
      },
    )
  })
  return () => {
    anims.forEach((a) => a.cancel())
    split.revert()
  }
}

function splitChars(el) {
  const text = el.textContent ?? ''

  if (el.children.length > 0) {
    return { chars: [el], words: [el], revert: () => {} }
  }

  const escape = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')

  const segmenter =
    typeof Intl !== 'undefined' && 'Segmenter' in Intl
      ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
      : null
  const toChars = (s) => (segmenter ? Array.from(segmenter.segment(s), (seg) => seg.segment) : Array.from(s))

  const tokens = text.match(/\S+|\s+/g) ?? []
  el.setAttribute('aria-label', text)
  el.innerHTML = tokens
    .map((token) => {
      if (/^\s+$/.test(token)) return token
      const chars = toChars(token)
        .map((c) => `<span class="k-char">${escape(c)}</span>`)
        .join('')
      return `<span class="k-word" aria-hidden="true">${chars}</span>`
    })
    .join('')

  return {
    chars: [...el.querySelectorAll('.k-char')],
    words: [...el.querySelectorAll('.k-word')],
    revert: () => {
      el.textContent = text
      el.removeAttribute('aria-label')
    },
  }
}