Scatter to Word
Characters spring from random positions and snap together into a word with elastic bounce — on load or on click.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →
3 usage examples



How it works
The utility function splitChars(t) reads the data-text attribute of the .kt-scatter container, clears its innerHTML, and re-injects one <span class="char"> per character. Each span is then given a random inline CSS transform — translate(Xpx, Ypx) rotate(Zdeg) — within roughly ±200 px horizontally and ±130 px vertically, plus opacity: 0: the letters are scattered away from their natural positions before anything is shown.
Assembly is driven entirely by CSS. The rule .kt-scatter.active .char forces opacity: 1 !important and transform: translate(0, 0) rotate(0deg) !important. The transition: .8s cubic-bezier(.34, 1.56, .64, 1) on each .char drives the movement — the second parameter of the cubic-bezier (1.56, above 1) produces a slight overshoot before settling: that's the elastic bounce, with no @keyframes involved.
Re-triggering on click removes the .active class, assigns fresh random positions to each span, then re-adds .active after ~80 ms. That minimal delay lets the browser apply the new starting transforms before launching the new transition. Without it, the transform property wouldn't have changed state and no new transition would fire.
The CSS bundle covers several text-animation variants sharing the same class engine: .kt-wave (sinusoidal float via @keyframes), .kt-elastic (appear by scaleY), .kt-liquid (per-letter hover deformation), .kt-whirlpool, .kt-explosion. Only .kt-scatter is documented here. The whole bundle is self-contained: no canvas, no external dependency.
Accessibility
- prefers-reduced-motion: absent from the code — the 0.8 s transition fires regardless of the system setting. Recommended fix:
@media (prefers-reduced-motion: reduce) { .kt-scatter .char { transition: none; opacity: 1 !important; transform: none !important; } } - Each
<span class="char">contains real text — screen readers will spell the word letter by letter. For natural word-level reading, addaria-label="[WORD]"on the container andaria-hidden="true"on each.char. - Text contrast: white (
#fff) on dark background (#0a0a0f) — ratio ≥ 10.5:1, well above the WCAG AAA threshold (7:1). - Keyboard interaction: the click re-trigger has no keyboard equivalent in the code. For accessibility, add
tabindex="0"on the container and akeydownhandler for Enter / Space. user-select: noneon.kt-textis appropriate for purely decorative text — remove it if the displayed word conveys information the user may need to copy.
Browser compatibility
Requires CSS Transitions and 2D transform — no canvas, no external dependency. cubic-bezier values above 1 (spring easing) are supported in all modern browsers since 2020.
If JS does not run, the <code><span class="char"></code> elements stay at <code>opacity: 0</code> (invisible). Provide a <code><noscript></code> fallback or display the word via <code>content: attr(data-text)</code> on a pseudo-element of the container.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="kt-text kt-scatter" id="scatter1" data-text="YOUR WORD">
<!-- <span class="char"> elements are injected by splitChars() on init -->
<!-- A single data-text attribute is all that's needed -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| data-text | SCATTER | The word to display — set this HTML attribute on the .kt-scatter container to change the word without touching JS. Can be updated dynamically before removing then re-adding the .active class. |
| .kt-text font-size | 2.2rem | Letter size. Go up to 4–6 rem for a full-screen hero, down to 1.4 rem for inline body use. |
| .kt-text color | #fff | Character color. Accepts any CSS value — for a gradient, apply background: linear-gradient(...); background-clip: text; -webkit-text-fill-color: transparent; on .kt-text. |
| .kt-scatter .char transition-duration | 0.8s | Assembly animation duration. 0.5 s = snappy, 1.2 s = cinematic. |
| cubic-bezier(.34, 1.56, .64, 1) | (spring elastic) | Animation curve. Increase the 2nd parameter (1.56) for more bounce; swap for ease-out for a straight deceleration without overshoot; cubic-bezier(.22, 1, .36, 1) for a softer spring. |
| .kt-text letter-spacing | .02em | Letter spacing in the assembled state. Go up to .08–.15 em for wide display style. |
| .kt-text font-weight | 800 | Font weight. 700 for normal bold, 900 for maximum impact if the font supports it. |
| Scatter radius (JS) | ±200 px / ±130 px | Starting random positions are computed over ±200 px horizontal and ±130 px vertical in the init script — reduce to ±100 px for a compact scatter, increase to ±300 px for a dramatic off-screen explosion. |
FAQ
splitChars(t) as a splitting utility. The full initialisation — random position assignment, firing .active on load and on click — is wired in the <script data-scenes> bundled with the examples. Copy that logic into your own script: it's about twenty self-contained lines with no dependencies.splitChars on each .kt-scatter container with a distinct id, assign random positions to each set of .char spans, then trigger .active independently. CSS transitions are scoped to the container: no interference between instances.data-text, remove .active, call splitChars(el) to regenerate the spans, assign random positions to each new .char, then re-add .active after ~80 ms. The word changes on each trigger without a page reload — useful for a quiz that reveals a different word on each question.