30 CSS Text Animations19 / 30

Pure CSSMIT licensed

CSS Text Hover Scatter Gather Letters

Letters as loose particles: a word that idles as blurred, scattered debris and springs into crisp alignment on hover (with real linear() spring physics), and its inverse — a tidy word that detonates apart under the cursor.

Published Updated

Live Demo
Try it

The code

<div class="cat-19-group">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap" rel="stylesheet">
<section class="cat-19a" aria-label="Scattered letters gather on hover">
  <div class="cat-19a__stage">
    <h1 class="cat-19a__word" aria-label="TOGETHER" tabindex="0">
      <span aria-hidden="true"><i style="--x:-44px;--y:-30px;--r:-22deg">T</i><i style="--x:26px;--y:38px;--r:14deg">O</i><i style="--x:-18px;--y:24px;--r:-9deg">G</i><i style="--x:40px;--y:-26px;--r:24deg">E</i><i style="--x:-32px;--y:-12px;--r:12deg">T</i><i style="--x:22px;--y:30px;--r:-18deg">H</i><i style="--x:-26px;--y:-34px;--r:8deg">E</i><i style="--x:38px;--y:16px;--r:-14deg">R</i></span>
    </h1>
    <p class="cat-19a__sub" aria-hidden="true">hover — every letter springs home from its own offset</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,500..900;1,500..900&display=swap" rel="stylesheet">
<section class="cat-19b" aria-label="Word explodes into letters on hover">
  <div class="cat-19b__stage">
    <h1 class="cat-19b__word" aria-label="SCATTER" tabindex="0">
      <span aria-hidden="true"><i style="--x:-60px;--y:-44px;--r:-38deg;--i:0">S</i><i style="--x:44px;--y:-58px;--r:26deg;--i:1">C</i><i style="--x:-38px;--y:50px;--r:-20deg;--i:2">A</i><i style="--x:60px;--y:34px;--r:34deg;--i:3">T</i><i style="--x:-52px;--y:-20px;--r:16deg;--i:4">T</i><i style="--x:36px;--y:56px;--r:-30deg;--i:5">E</i><i style="--x:-24px;--y:-60px;--r:22deg;--i:6">R</i></span>
    </h1>
    <p class="cat-19b__sub" aria-hidden="true">hover to detonate — release to reassemble</p>
  </div>
</section>
</div>
.cat-19-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-19-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-19a,
.cat-19a *,
.cat-19a *::before,
.cat-19a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-19a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #f3efe6;
  font-family: 'Playfair Display',Georgia,serif;
}

.cat-19a__stage {
  text-align: center;
}

.cat-19a__word {
  display: inline-block;
  padding: 50px clamp(24px,5vw,70px);
  font-size: clamp(40px,7.6vw,96px);
  font-weight: 700;
  letter-spacing: .05em;
  color: #241f18;
  cursor: pointer;
}

.cat-19a__word i {
  display: inline-block;
  font-style: normal;
  opacity: .38;
  filter: blur(2.5px);
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  transition: transform .75s,filter .55s,opacity .55s;
  transition-timing-function: cubic-bezier(.34,1.45,.5,1);
  transition-timing-function: linear(0,0.008 6.3%,0.235 20.4%,0.685 39.8%,1.02 58%,1.11 66.7%,1.03 82%,0.99 92%,1);
}

.cat-19a__word:hover i,
.cat-19a__word:focus-visible i {
  opacity: 1;
  filter: blur(0);
  transform: translate(0,0) rotate(0);
}

.cat-19a__word:focus-visible {
  outline: 2px dashed rgba(36,31,24,.5);
  outline-offset: 8px;
}

.cat-19a__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .15em;
  color: rgba(36,31,24,.42);
}

@media (prefers-reduced-motion: reduce) {
  .cat-19a__word i {
    opacity: 1;
    filter: none;
    transform: none;
    transition: none;
  }
}

.cat-19b,
.cat-19b *,
.cat-19b *::before,
.cat-19b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-19b {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #0e1015;
}

.cat-19b__stage {
  text-align: center;
}

.cat-19b__word {
  display: inline-block;
  padding: 60px clamp(24px,5vw,80px);
  font-family: 'Archivo','Segoe UI',system-ui,sans-serif;
  font-size: clamp(40px,7.6vw,96px);
  font-weight: 900;
  letter-spacing: .03em;
  cursor: crosshair;
}

.cat-19b__word i {
  display: inline-block;
  font-style: normal;
  color: oklch(0.82 0.17 calc(var(--i)*52));
  transition: transform .55s cubic-bezier(.2,.8,.25,1.15),filter .55s;
  transition-delay: calc(var(--i)*28ms);
}

.cat-19b__word:hover i,
.cat-19b__word:focus-visible i {
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  filter: blur(.8px);
}

.cat-19b__word:focus-visible {
  outline: 2px dashed rgba(240,244,255,.4);
  outline-offset: 8px;
}

.cat-19b__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .15em;
  color: rgba(190,205,235,.4);
}

@media (prefers-reduced-motion: reduce) {
  .cat-19b__word i {
    transition: none;
  }

  .cat-19b__word:hover i,
    .cat-19b__word:focus-visible i {
    transform: none;
    filter: none;
  }
}
/* No JavaScript — inline --x/--y/--r map each letter's scatter pose; hovering the word transitions all of them to identity with a linear() spring. */

/* No JavaScript — the inverse gather: identity at rest, inline scatter poses on hover, with --i transition-delays rippling the blast outward. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Text Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Text Hover Scatter Gather Letters
Source: https://codefronts.com/motion/css-text-animations/css-split-text-explosion-animation/

Letters as loose particles: a word that idles as blurred, scattered debris and springs into crisp alignment on hover (with real linear() spring physics), and its inverse — a tidy word that detonates apart under the cursor.
## HTML
```html
<div class="cat-19-group">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap" rel="stylesheet">
<section class="cat-19a" aria-label="Scattered letters gather on hover">
  <div class="cat-19a__stage">
    <h1 class="cat-19a__word" aria-label="TOGETHER" tabindex="0">
      <span aria-hidden="true"><i style="--x:-44px;--y:-30px;--r:-22deg">T</i><i style="--x:26px;--y:38px;--r:14deg">O</i><i style="--x:-18px;--y:24px;--r:-9deg">G</i><i style="--x:40px;--y:-26px;--r:24deg">E</i><i style="--x:-32px;--y:-12px;--r:12deg">T</i><i style="--x:22px;--y:30px;--r:-18deg">H</i><i style="--x:-26px;--y:-34px;--r:8deg">E</i><i style="--x:38px;--y:16px;--r:-14deg">R</i></span>
    </h1>
    <p class="cat-19a__sub" aria-hidden="true">hover — every letter springs home from its own offset</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,500..900;1,500..900&display=swap" rel="stylesheet">
<section class="cat-19b" aria-label="Word explodes into letters on hover">
  <div class="cat-19b__stage">
    <h1 class="cat-19b__word" aria-label="SCATTER" tabindex="0">
      <span aria-hidden="true"><i style="--x:-60px;--y:-44px;--r:-38deg;--i:0">S</i><i style="--x:44px;--y:-58px;--r:26deg;--i:1">C</i><i style="--x:-38px;--y:50px;--r:-20deg;--i:2">A</i><i style="--x:60px;--y:34px;--r:34deg;--i:3">T</i><i style="--x:-52px;--y:-20px;--r:16deg;--i:4">T</i><i style="--x:36px;--y:56px;--r:-30deg;--i:5">E</i><i style="--x:-24px;--y:-60px;--r:22deg;--i:6">R</i></span>
    </h1>
    <p class="cat-19b__sub" aria-hidden="true">hover to detonate — release to reassemble</p>
  </div>
</section>
</div>
```
## CSS
```css
.cat-19-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-19-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-19a,
.cat-19a *,
.cat-19a *::before,
.cat-19a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-19a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #f3efe6;
  font-family: 'Playfair Display',Georgia,serif;
}

.cat-19a__stage {
  text-align: center;
}

.cat-19a__word {
  display: inline-block;
  padding: 50px clamp(24px,5vw,70px);
  font-size: clamp(40px,7.6vw,96px);
  font-weight: 700;
  letter-spacing: .05em;
  color: #241f18;
  cursor: pointer;
}

.cat-19a__word i {
  display: inline-block;
  font-style: normal;
  opacity: .38;
  filter: blur(2.5px);
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  transition: transform .75s,filter .55s,opacity .55s;
  transition-timing-function: cubic-bezier(.34,1.45,.5,1);
  transition-timing-function: linear(0,0.008 6.3%,0.235 20.4%,0.685 39.8%,1.02 58%,1.11 66.7%,1.03 82%,0.99 92%,1);
}

.cat-19a__word:hover i,
.cat-19a__word:focus-visible i {
  opacity: 1;
  filter: blur(0);
  transform: translate(0,0) rotate(0);
}

.cat-19a__word:focus-visible {
  outline: 2px dashed rgba(36,31,24,.5);
  outline-offset: 8px;
}

.cat-19a__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .15em;
  color: rgba(36,31,24,.42);
}

@media (prefers-reduced-motion: reduce) {
  .cat-19a__word i {
    opacity: 1;
    filter: none;
    transform: none;
    transition: none;
  }
}

.cat-19b,
.cat-19b *,
.cat-19b *::before,
.cat-19b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-19b {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #0e1015;
}

.cat-19b__stage {
  text-align: center;
}

.cat-19b__word {
  display: inline-block;
  padding: 60px clamp(24px,5vw,80px);
  font-family: 'Archivo','Segoe UI',system-ui,sans-serif;
  font-size: clamp(40px,7.6vw,96px);
  font-weight: 900;
  letter-spacing: .03em;
  cursor: crosshair;
}

.cat-19b__word i {
  display: inline-block;
  font-style: normal;
  color: oklch(0.82 0.17 calc(var(--i)*52));
  transition: transform .55s cubic-bezier(.2,.8,.25,1.15),filter .55s;
  transition-delay: calc(var(--i)*28ms);
}

.cat-19b__word:hover i,
.cat-19b__word:focus-visible i {
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  filter: blur(.8px);
}

.cat-19b__word:focus-visible {
  outline: 2px dashed rgba(240,244,255,.4);
  outline-offset: 8px;
}

.cat-19b__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .15em;
  color: rgba(190,205,235,.4);
}

@media (prefers-reduced-motion: reduce) {
  .cat-19b__word i {
    transition: none;
  }

  .cat-19b__word:hover i,
    .cat-19b__word:focus-visible i {
    transform: none;
    filter: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — inline --x/--y/--r map each letter's scatter pose; hovering the word transitions all of them to identity with a linear() spring. */

/* No JavaScript — the inverse gather: identity at rest, inline scatter poses on hover, with --i transition-delays rippling the blast outward. */
```

How this works

Each letter carries its own scatter destination as inline custom properties; the base state applies them, hover resolves everything to identity:

<i style="--x:-38px;--y:22px;--r:-24deg">E</i>
i{transform:translate(var(--x),var(--y)) rotate(var(--r));
  filter:blur(2px);opacity:.4;
  transition:transform .7s linear(0,1.14 60%,.98 80%,1),
             filter .5s,opacity .5s}
.word:hover i{transform:none;filter:blur(0);opacity:1}

Because every letter travels a different distance under one shared duration, the gather lands organically — no delays needed. The explode variant flips the states and adds per-letter transition delays so the blast ripples outward from the first letter.

Make it yours

  • Scatter radius: keep |x| under ~0.8em × word length so letters never leave the stage.
  • Add per-letter transition-delay for a wave-style gather instead of simultaneous.
  • Explode variant: raise rotate values (±60°) for cartoon chaos.
  • Trigger from a parent section (:hover on the hero) so the word assembles as users approach.

Gotchas — read before shipping

  • Letters must be inline-block; transforms are ignored on inline boxes.
  • Blur is per-letter here — cheap; blurring the whole word each frame would rasterize the group repeatedly.
  • Provide the assembled word to assistive tech via aria-label; the scattered spans are aria-hidden.

Browser support

ChromeSafariFirefoxEdge
113+17.2+112+113+

Versions reflect linear() spring easing; with the cubic-bezier fallback line included, the effect works in every evergreen browser.

Search CodeFronts

Loading…