テック技報最新号へ →
CSS

ローディングスピナーは animation-delay で 200ms 遅らせて表示する

スピナーを opacity: 0 で置き、animation-delay: 200ms のフェードインで出すと、すぐ終わる処理ではスピナーが一瞬チラつかなくなる。

@sanyampunia · 投稿 2026-11-09

どんな時に使う?

保存ボタンや送信ボタンで、処理中にスピナーを出したいとき。100ms で終わる処理でもスピナーを即表示すると、一瞬だけ点滅して「何か起きた?」という落ち着かない見た目になる。逆に、一度出したスピナーがすぐ消えるのも同じくチラつきに見える。そこで「200ms 経ってから出す」「出したら最低 400ms は見せる」の2段構えにする。

コード

css
.spinner {
  opacity: 0;
  /* 1つ目: 200ms 待ってから表示(長さ0で一気に opacity: 1 にする)
     2つ目: 回転は最初から動かしておく */
  animation:
    show 0s 200ms forwards,
    spin 0.8s linear infinite;
}

@keyframes show {
  to { opacity: 1; }
}

@keyframes spin {
  to { rotate: 1turn; }
}

「最低 400ms は見せる」部分は CSS だけではできないので、数行の JS で処理の終了を待たせる。

js
const start = performance.now();
button.append(spinner);
await save();
// 表示され始めてから経った時間(200ms の待ちを引く)
const shown = performance.now() - start - 200;
if (shown > 0) {
  await new Promise((r) => setTimeout(r, 400 - shown));
}
spinner.remove();
  • 200ms 以内に終わった処理では shown が 0 以下になり、スピナーは一度も描画されないまま消える。
  • 表示された場合は、表示開始から 400ms に満たなければ残りの時間だけ待ってから消す。
  • フェードを滑らかにしたいなら show 150ms 200ms ease-out forwards のように長さを付けてもよい。

注意点・対応状況

  • animation-delay と forwards はすべての主要ブラウザで使える。
  • opacity: 0 の間もスピナーは要素として存在するので、スクリーンリーダー向けには aria-busy="true" やライブリージョンでの「保存中」通知を別に用意する。
  • 200ms・400ms は目安。処理時間の分布を見て調整する。
  • prefers-reduced-motion: reduce の利用者には回転を止めて、表示だけにするなどの配慮をする。

出典

X の元の投稿を開く