25 CSS Spinners 20 / 25
Particle Scatter Burst Spinner
Eight coloured particles radiate outward from a central white core, fading and shrinking to nothing at the edge of their orbit while the whole assembly rotates slowly.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="sp-20">
<div class="sp-20__burst">
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__core"></div>
</div>
</div> <div class="sp-20">
<div class="sp-20__burst">
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__particle"></div>
<div class="sp-20__core"></div>
</div>
</div>.sp-20,.sp-20 *,.sp-20 *::before,.sp-20 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-20{
--bg:#08050f;
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-20__burst{
position:relative;
width:100px;
height:100px;
animation:sp-20-rotate 3s linear infinite;
}
.sp-20__particle{
position:absolute;
top:50%;
left:50%;
width:8px;
height:8px;
border-radius:50%;
margin:-4px 0 0 -4px;
animation:sp-20-scatter 1.5s ease-out infinite;
}
.sp-20__particle:nth-child(1){background:#ff6b6b;animation-delay:0s;transform:rotate(0deg)}
.sp-20__particle:nth-child(2){background:#ffd93d;animation-delay:0.1s;transform:rotate(45deg)}
.sp-20__particle:nth-child(3){background:#6bcb77;animation-delay:0.2s;transform:rotate(90deg)}
.sp-20__particle:nth-child(4){background:#4d96ff;animation-delay:0.3s;transform:rotate(135deg)}
.sp-20__particle:nth-child(5){background:#ff6b6b;animation-delay:0.4s;transform:rotate(180deg)}
.sp-20__particle:nth-child(6){background:#ffd93d;animation-delay:0.5s;transform:rotate(225deg)}
.sp-20__particle:nth-child(7){background:#6bcb77;animation-delay:0.6s;transform:rotate(270deg)}
.sp-20__particle:nth-child(8){background:#4d96ff;animation-delay:0.7s;transform:rotate(315deg)}
@keyframes sp-20-scatter{
0%{transform:var(--r,rotate(0deg)) translateX(0) scale(1);opacity:1}
60%{opacity:0.8}
100%{transform:var(--r,rotate(0deg)) translateX(42px) scale(0);opacity:0}
}
.sp-20__particle:nth-child(1){--r:rotate(0deg)}
.sp-20__particle:nth-child(2){--r:rotate(45deg)}
.sp-20__particle:nth-child(3){--r:rotate(90deg)}
.sp-20__particle:nth-child(4){--r:rotate(135deg)}
.sp-20__particle:nth-child(5){--r:rotate(180deg)}
.sp-20__particle:nth-child(6){--r:rotate(225deg)}
.sp-20__particle:nth-child(7){--r:rotate(270deg)}
.sp-20__particle:nth-child(8){--r:rotate(315deg)}
@keyframes sp-20-rotate{
to{transform:rotate(360deg)}
}
.sp-20__core{
position:absolute;
inset:40px;
border-radius:50%;
background:white;
box-shadow:0 0 12px white;
animation:sp-20-pulse 1.5s ease-in-out infinite;
}
@keyframes sp-20-pulse{
0%,100%{transform:scale(0.8);opacity:0.6}
50%{transform:scale(1.2);opacity:1}
}
@media (prefers-reduced-motion: reduce){
.sp-20__particle,.sp-20__burst,.sp-20__core{animation:none}
} .sp-20,.sp-20 *,.sp-20 *::before,.sp-20 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-20{
--bg:#08050f;
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-20__burst{
position:relative;
width:100px;
height:100px;
animation:sp-20-rotate 3s linear infinite;
}
.sp-20__particle{
position:absolute;
top:50%;
left:50%;
width:8px;
height:8px;
border-radius:50%;
margin:-4px 0 0 -4px;
animation:sp-20-scatter 1.5s ease-out infinite;
}
.sp-20__particle:nth-child(1){background:#ff6b6b;animation-delay:0s;transform:rotate(0deg)}
.sp-20__particle:nth-child(2){background:#ffd93d;animation-delay:0.1s;transform:rotate(45deg)}
.sp-20__particle:nth-child(3){background:#6bcb77;animation-delay:0.2s;transform:rotate(90deg)}
.sp-20__particle:nth-child(4){background:#4d96ff;animation-delay:0.3s;transform:rotate(135deg)}
.sp-20__particle:nth-child(5){background:#ff6b6b;animation-delay:0.4s;transform:rotate(180deg)}
.sp-20__particle:nth-child(6){background:#ffd93d;animation-delay:0.5s;transform:rotate(225deg)}
.sp-20__particle:nth-child(7){background:#6bcb77;animation-delay:0.6s;transform:rotate(270deg)}
.sp-20__particle:nth-child(8){background:#4d96ff;animation-delay:0.7s;transform:rotate(315deg)}
@keyframes sp-20-scatter{
0%{transform:var(--r,rotate(0deg)) translateX(0) scale(1);opacity:1}
60%{opacity:0.8}
100%{transform:var(--r,rotate(0deg)) translateX(42px) scale(0);opacity:0}
}
.sp-20__particle:nth-child(1){--r:rotate(0deg)}
.sp-20__particle:nth-child(2){--r:rotate(45deg)}
.sp-20__particle:nth-child(3){--r:rotate(90deg)}
.sp-20__particle:nth-child(4){--r:rotate(135deg)}
.sp-20__particle:nth-child(5){--r:rotate(180deg)}
.sp-20__particle:nth-child(6){--r:rotate(225deg)}
.sp-20__particle:nth-child(7){--r:rotate(270deg)}
.sp-20__particle:nth-child(8){--r:rotate(315deg)}
@keyframes sp-20-rotate{
to{transform:rotate(360deg)}
}
.sp-20__core{
position:absolute;
inset:40px;
border-radius:50%;
background:white;
box-shadow:0 0 12px white;
animation:sp-20-pulse 1.5s ease-in-out infinite;
}
@keyframes sp-20-pulse{
0%,100%{transform:scale(0.8);opacity:0.6}
50%{transform:scale(1.2);opacity:1}
}
@media (prefers-reduced-motion: reduce){
.sp-20__particle,.sp-20__burst,.sp-20__core{animation:none}
}How this works
Each particle is positioned at the centre of the wrapper and uses a CSS custom property --r to set its rotation angle (0°, 45°, 90°, etc.). The sp-20-scatter keyframe combines var(--r) translateX(42px) — effectively moving each particle along its own radial line — while simultaneously scaling from 1 to 0 and fading to 0 opacity at the end point. This produces the explosion/scatter effect.
Eight particles with staggered animation-delay of 0.1s increments create a continuous stream where particles at different stages of their outward journey are always visible. The parent wrapper rotates slowly at 3s so the scatter directions slowly precess, adding depth.
Customize
- Change the 8 particle colours by editing the
backgroundvalue on eachnth-childrule. - Increase scatter radius from
42pxto60pxfor wider burst coverage. - Reduce particle size from
8pxto4pxand add more particles (16 at22.5degintervals) for a finer firework-style burst. - Remove the parent rotation (
sp-20-rotate) for a purely radial, symmetric scatter effect. - Change
animation-delayincrements to0.2sfor a slower, more deliberate scatter cadence.
Watch out for
- The CSS custom property
--rinside a keyframetransformvalue requires the browser to evaluate the property at animation time — this is supported in Chrome 66+, Firefox 61+, Safari 15.4+; earlier Safari needs the transform values written out explicitly. - Eight elements all animating simultaneously can create 8 compositor layers — use
will-change:transform,opacityselectively to pre-promote particles expected to animate. - The parent rotation and child scatter animations compose correctly only if the parent has no
transform-style:preserve-3d— 3D context would treat the children as 3D objects and produce incorrect depth sorting.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 66+ | 15.4+ | 61+ | 66+ |
CSS custom properties in keyframe transform values require Safari 15.4+; earlier Safari needs explicit transform values.