Dot Matrix Loader

도트 매트릭스 로더

Dots placed around a circle, brightening one after another so the ring appears to spin without anything actually rotating.

Also known as: LED dot loaderCircular dot spinner
···
html
<div class="wrap" role="status" aria-live="polite">
  <div class="dm" aria-hidden="true">
    <i style="--i:0"></i><i style="--i:1"></i><i style="--i:2"></i><i style="--i:3"></i>
    <i style="--i:4"></i><i style="--i:5"></i><i style="--i:6"></i><i style="--i:7"></i>
  </div>
  <span class="cap">점마다 opacity delay = 회전 없는 회전 착시</span>
</div>
css
.wrap{display:flex;flex-direction:column;align-items:center;gap:18px}
.dm{position:relative;width:clamp(38px,12vmin,68px);height:clamp(38px,12vmin,68px)}
.dm i{position:absolute;top:0;left:50%;width:16%;aspect-ratio:1;border-radius:50%;background:var(--accent);
  transform:rotate(calc(var(--i) * 45deg)) translate(-50%,0);transform-origin:50% calc(clamp(19px,6vmin,34px));
  animation:dm 1.2s linear infinite;animation-delay:calc(var(--i) * -.15s)}
@keyframes dm{0%{opacity:1}100%{opacity:.15}}
.cap{font-size:clamp(9px,2.4vmin,11px);color:var(--muted);letter-spacing:.02em}

The end result looks similar to a ring spinner — a circle that appears to spin — but the mechanism differs: nothing actually rotates, and dots at fixed positions simply brighten and dim in sequence. This is how iOS's built-in activity indicator works, and because only opacity is animated rather than transform: rotate, it stays smooth even on low-power devices.

8–12 dots are placed around a circle by rotating each one by (360° ÷ dot count), then every dot gets a keyframe that dims and restores its opacity, with animation-delay offset by (full cycle ÷ dot count) per dot. The rotate used for placement never animates — it just fixes each dot's position — which is exactly what avoids the jank a rotating transform can introduce, while still creating the illusion of spin.

A skeleton fits a long wait covering the whole screen better; skip this under 200ms. Give the wrapper role="status" and aria-live="polite", and hide the dots with aria-hidden="true" since they're decorative. Under prefers-reduced-motion, simplify the sequential brightening into the whole ring of dots pulsing faintly together.