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.
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> <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; }
} .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);
})(); (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
±200pxto±400px— letters fly in from further away for more drama. - Remove the rotation component by setting
--r: 0degon all letters for a pure positional explosion without tumbling. - Add a blur from a starting
filter: blur(4px)toblur(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.addcycle. - 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
transformwithtranslate(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: hiddenon the container to prevent layout bleed. - Animating
opacityandtransformsimultaneously on many elements is GPU-composited — but spawning more than 30 simultaneously animated elements may drop frames on mid-range mobile.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 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.