【CSS/JavaScript】画像を回転させ続ける方法|CSSアニメーションとrequestAnimationFrameで滑らかに

ローディングのスピナーやアイコンの装飾など、画像を回転させ続ける(くるくる回す)表現はよく使います。実現方法はCSSとJavaScriptの2通りありますが、基本はCSSが正解です。理由もあわせて見ていきましょう。

この記事の結論:回し続けるだけならCSSの animation: spin Ns linear infinite が最もシンプルで滑らか(GPU合成で軽い)です。スクロール量やユーザー操作に連動させたいときだけJavaScriptを使い、その場合は setInterval ではなく requestAnimationFrame + 経過時間で実装します。
スポンサーリンク

CSSで回転させ続ける(推奨)

@keyframes で 0deg→360deg の回転を定義し、animationinfinite(無限ループ)と linear(等速)を指定します。これだけで回り続けます。

回転アニメーション
.rotate-image {
  animation: spin 2s linear infinite; /* 2秒で1回転・等速・無限 */
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
速度は animation-duration で調整します。2s より 1s のほうが速く5s にするとゆっくり回ります(1回転にかかる時間)。回転の中心をずらしたいときは transform-origin を指定します(既定は中心)。

滑らかに回すコツ(GPU合成)

transform による回転はGPUで合成されるため、レイアウトの再計算が起きず軽快です。さらに will-change: transform を添えると、ブラウザに「この要素は変化する」と事前に伝えられます。

GPUヒント
.rotate-image {
  animation: spin 2s linear infinite;
  will-change: transform; /* 合成の最適化ヒント(多用は避ける) */
}
will-change常時動く要素に限って使います。ページ中の多くの要素に付けるとメモリを消費し逆効果です。top/left で動かすと再レイアウトが走り重くなるため、回転・移動はtransform で行うのが鉄則です。

動きを抑える設定に配慮する(prefers-reduced-motion)

無限に回り続けるアニメーションは、めまいや乗り物酔いを起こしやすいユーザーに負担になります。OSの「視差効果を減らす/動きを減らす」設定を尊重するため、@media (prefers-reduced-motion: reduce)アニメーションを止めるのがアクセシビリティの基本です。

動きを減らす設定を尊重
@media (prefers-reduced-motion: reduce) {
  .rotate-image {
    animation: none; /* 回転を止める */
  }
}

マウスを乗せたら一時停止する

animation-play-state を切り替えるだけで、アニメーションをその場で一時停止・再開できます。JavaScriptは不要です。

hoverで停止
.rotate-image:hover {
  animation-play-state: paused; /* マウスオーバー中だけ止まる */
}

JavaScriptで制御する(requestAnimationFrame)

スクロール量やボタン操作と連動させたいときはJavaScriptを使います。ただし setInterval使いませんrequestAnimationFrame なら画面の更新タイミングに同期して滑らかで、背景タブでは自動的に止まる(電池に優しい)からです。

ポイントは「経過時間(delta time)で角度を進める」こと。フレームごとに固定値を足すと、端末のフレームレートで速度が変わってしまいます。経過秒数を掛ければどの環境でも同じ速度になります。

rAF + 経過時間で一定速度
const el = document.querySelector(".rotate-image");
const degPerSec = 180; // 1秒あたり180度回す
let angle = 0;
let last = null;
let rafId;

function tick(now) {
  if (last !== null) {
    const dt = (now - last) / 1000;       // 経過秒数
    angle = (angle + degPerSec * dt) % 360;
    el.style.transform = `rotate(${angle}deg)`;
  }
  last = now;
  rafId = requestAnimationFrame(tick);
}

rafId = requestAnimationFrame(tick);

// 停止したいとき
// cancelAnimationFrame(rafId);
setInterval が非推奨な理由:setInterval(fn, 50) は約20fpsでカクつき、固定値を足す方式は更新間隔で速度が変わって不安定です(同じ「2度ずつ」でも50ms間隔なら1秒で40度、25ms間隔なら80度になる)。requestAnimationFrame + 経過時間なら速度が一定に保たれます。

逆回転・3D風の回転にする

回転方向や軸を変えるのも簡単です。逆回転は終了角度をマイナスに、立体的な反転は rotateY を使います。

逆回転・Y軸回転
/* 逆回転(反時計回り) */
@keyframes spin-reverse {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}

/* Y軸でコインのように回す */
@keyframes flip-y {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

CSS と JavaScript の使い分け

状況 おすすめ
ただ回し続ける/ローディング表示 CSSanimation infinite
速度を一定に保ちたい CSS、またはJSならrAF+経過時間
スクロール量・操作に連動 JavaScriptrequestAnimationFrame
hoverで一時停止 CSS(animation-play-state

よくある質問(FAQ)

Q画像を回転させ続ける一番簡単な方法は?
ACSSで animation: spin 2s linear infinite; を指定し、@keyframes spin0deg360deg を定義する方法です。JavaScript不要で滑らかに回り続けます。
Q回転の速度を変えるには?
ACSSなら animation-duration を変えます。値が小さいほど速く、大きいほどゆっくり回ります(1回転にかかる時間)。JavaScriptなら回転量を「1秒あたり何度」で持ち、経過時間(delta time)を掛けて加算すると速度が安定します。
QsetInterval で回すとカクつくのはなぜ?
AsetInterval は画面の更新タイミングと無関係に動くためフレーム落ちしやすく、背景タブでも回り続けます。requestAnimationFrame は描画に同期して滑らかで、非表示タブでは自動的に止まるため省電力です。
Qアニメーションが苦手なユーザーへの配慮は必要ですか?
A必要です。無限アニメはめまいの原因になり得るため、@media (prefers-reduced-motion: reduce)animation: none; を指定し、OSの「動きを減らす」設定を尊重しましょう。

まとめ

画像を回転させ続ける方法のポイントを整理します。

  • 基本は CSS animation: spin Ns linear infinite(軽くて滑らか)
  • 速度は animation-duration、中心は transform-origin
  • 回転・移動は transform で(top/left は重い)
  • prefers-reduced-motion で停止してアクセシビリティに配慮
  • JS連動は requestAnimationFrame + 経過時間setInterval は使わない)

関連として、同じ requestAnimationFrame を使う数字をカウントアップアニメーションさせる方法ページを自動スクロールさせる方法もあわせて読むと、滑らかなアニメーション制御に強くなれます。