25 CSS Text Animations 19 / 25

CSS Split Text Explosion Animation

Letters fly in from random off-screen positions and converge to form a word — a dramatic entrance with JS-randomised transform origins.

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

The code

<div class="ta-19">
  <div class="ta-19__stage">
    <h2 class="ta-19__text" id="ta-19-text" aria-label="IMPACT">IMPACT</h2>
    <button class="ta-19__btn" id="ta-19-replay">↺ Replay</button>
  </div>
</div>
.ta-19, .ta-19 *, .ta-19 *::before, .ta-19 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.ta-19 ::selection { background: #9f1239; color: #fff; }

.ta-19 {
  --bg: radial-gradient(ellipse at 50% 60%, #1c0020, #050008);
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 2rem;
  font-family: 'Syne', 'Helvetica Neue', sans-serif;
  overflow: hidden;
}

.ta-19__text {
  font-size: clamp(3rem, 10vw, 6rem);
  font-weight: 900;
  letter-spacing: 0.08em;
  display: flex;
  justify-content: center;
  gap: 0.02em;
}

.ta-19__char {
  display: inline-block;
  background: linear-gradient(135deg, #f43f5e, #fb923c);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  opacity: 0;
  animation: ta-19-fly 0.75s cubic-bezier(0.34, 1.4, 0.64, 1) forwards;
}

@keyframes ta-19-fly {
  from {
    opacity: 0;
    transform: translate(var(--tx, 0px), var(--ty, -60px)) rotate(var(--r, 0deg));
  }
  to {
    opacity: 1;
    transform: translate(0, 0) rotate(0deg);
  }
}

.ta-19__btn {
  font-family: inherit;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: none;
  border: 1px solid #4a0a20;
  color: #9f1239;
  padding: 0.4rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}

.ta-19__btn:hover { border-color: #f43f5e; color: #f43f5e; }

@media (prefers-reduced-motion: reduce) {
  .ta-19__char { animation: none; opacity: 1; transform: none; }
}
(function() {
  const wrap = document.getElementById('ta-19-text');
  const btn  = document.getElementById('ta-19-replay');
  if (!wrap) return;

  function explode() {
    const word = wrap.getAttribute('aria-label') || wrap.textContent;
    wrap.innerHTML = '';
    [...word].forEach((ch, i) => {
      const span = document.createElement('span');
      span.className = 'ta-19__char';
      span.textContent = ch;
      const angle = Math.random() * Math.PI * 2;
      const dist  = 120 + Math.random() * 180;
      span.style.setProperty('--tx', Math.cos(angle) * dist + 'px');
      span.style.setProperty('--ty', Math.sin(angle) * dist + 'px');
      span.style.setProperty('--r',  (Math.random() * 60 - 30) + 'deg');
      span.style.animationDelay = (i * 0.06) + 's';
      wrap.appendChild(span);
    });
  }

  explode();
  if (btn) btn.addEventListener('click', explode);
})();

How this works

JavaScript splits the text into individual letter spans and assigns each a random --tx (translateX) and --ty (translateY) CSS custom property — values like ±200px in random directions. The CSS keyframe reads these properties via translate(var(--tx), var(--ty)) as the starting state and animates to translate(0, 0), so each letter flies in from its own unique off-screen position.

The animation also starts with opacity: 0 and rotate: var(--r) (a random rotation up to ±45°) and eases to opacity: 1 and rotate: 0deg. Staggered delays ensure letters arrive at slightly different times, creating the chaos-to-order resolution of an explosion played in reverse. The cubic-bezier easing overshoots slightly so letters briefly pass their resting position before snapping into place.

Customize

  • Increase the explosion radius by widening the random range from ±200px to ±400px — letters fly in from further away for more drama.
  • Remove the rotation component by setting --r: 0deg on all letters for a pure positional explosion without tumbling.
  • Add a blur from a starting filter: blur(4px) to blur(0) in the keyframe for letters that materialize out of a haze as they arrive.
  • Trigger on click instead of page load by wrapping the split logic in a click handler and resetting the animation with a brief classList.remove / void el.offsetWidth / classList.add cycle.
  • Use a two-phase animation — explosion in, then gentle drift out after a hold — to create a looping attention-grabbing hero word effect.

Watch out for

  • Using CSS custom properties inside transform with translate(var(--tx)) requires the browser to resolve the variable before compositing — ensure variables are set as inline styles before the animation class is applied.
  • Very large translate offsets can cause scrollbars to appear if the page body isn't clipped; wrap the entire demo in overflow: hidden on the container to prevent layout bleed.
  • Animating opacity and transform simultaneously on many elements is GPU-composited — but spawning more than 30 simultaneously animated elements may drop frames on mid-range mobile.

Browser support

ChromeSafariFirefoxEdge
All All All All

CSS custom properties in transform values are supported in all modern browsers. Test that inline style assignment happens synchronously before CSS animation begins.

Search CodeFronts

Loading…