jQueryアニメーションがカクつく理由とrequestAnimationFrameの関係

カクつきの原因は「速さ」ではなく「タイミング」

jQueryのアニメーションが重いと言われると、処理が遅いからだと思われがちです。
しかし実際の原因は速度ではありません。
ブラウザの描画タイミングと同期していないことです。

jQueryの`fadeIn()`や`animate()`は十分に軽い処理です。
それでもカクつくのは、描画の仕組みとズレているためです。

ブラウザは常に描画しているわけではない

ブラウザはプログラムが変更するたびに画面を描いているわけではありません。
一定周期でまとめて描画します。

多くの環境では1秒間に約60回です。
つまり約16.6msごとに1フレーム描画されます。

これをフレーム更新と呼びます。

ブラウザ内部では

  • JavaScript実行
  • スタイル計算
  • レイアウト計算
  • 描画

がこの周期で処理されます。

jQueryアニメーションの仕組み

jQueryのアニメーションはタイマーで動きます。

$("#box").animate({ left: "200px" }, 400);

内部ではおおよそ次の処理が行われています。

  • setInterval(またはsetTimeout)開始
  • 一定間隔で数値を更新
  • styleを書き換え
  • 終了まで繰り返し

問題は、このタイマーがブラウザの描画周期を知らないことです。

何が起きているのか

例えば13ms間隔で更新するとします。

  • 16msで描画される
  • 13msでJavaScriptが動く

この2つは同期していません。

結果:

  • 描画前に2回更新される
  • 描画直後に更新される
  • 無駄な再計算が起きる

つまり、描かれないフレームが発生します。
これがカクつきです。

CPUが速くても滑らかにならない理由はここです。

requestAnimationFrameとは何か

ここで登場するのが`requestAnimationFrame`です。

requestAnimationFrame(step);

これは「次の描画直前に呼び出してほしい」というブラウザへの依頼です。

ブラウザは

  • 描画の直前
  • 最適なタイミング

でコールバックを実行します。

つまり、JavaScript更新と描画が同期します。

なぜ滑らかになるのか

違いを比較します。

方式 更新タイミング
setInterval 固定時間
requestAnimationFrame 描画直前

`requestAnimationFrame`では次が起きます。

  • 無駄な更新がない
  • フレーム落ちが減る
  • CPU使用率が下がる
  • バッテリー消費が減る

特にノートPCやスマートフォンで差が出ます。

jQueryが不利になる理由

jQueryが設計された時代、`requestAnimationFrame`は存在していませんでした。
そのためタイマー方式を採用しています。

問題は、タイマー方式だと以下が起きることです。

  • バックグラウンドタブでも動く
  • 描画されないのに計算する
  • スクロールと競合する

これがアニメーション中のスクロールカクつきの原因になります。

実際のカクつきパターン

よくある現象です。

  • メニューを開くとスクロールが重い
  • モーダル表示中に入力が遅れる
  • スマホで操作不能になる

これはCPUが足りないのではなく、
描画とJavaScriptが取り合いをしている状態です。

特に重いプロパティ

さらに問題を悪化させるのがレイアウト変更です。

$("#box").animate({ top: "200px" });

top/leftはレイアウト再計算(reflow)を発生させます。
1フレームごとにレイアウト計算が走ります。

一方で次は軽くなります。

element.style.transform = "translateY(200px)";

transformはレイアウトを変えず、GPU合成レイヤーで処理されます。

ではjQueryは使えないのか

そういうわけではありません。
短いUIアニメーションなら問題になることは少ないです。

問題が出るのは

  • 長時間アニメーション
  • 同時アニメーション多数
  • スクロール連動

のときです。

対策の方向性

代表的な改善策です。

  • CSS transitionを使う
  • transform/opacityを使う
  • requestAnimationFrameベースのライブラリを使う
  • アニメーション数を減らす

例えばCSSならこうです。

.box{
  transition: transform .4s ease;
}
.box.open{
  transform: translateY(200px);
}

JavaScriptはクラスを切り替えるだけになります。

最後に

jQueryアニメーションがカクつくのは性能不足ではありません。
描画システムと協調していない設計によるものです。

`requestAnimationFrame`は単なる新しいAPIではなく、
ブラウザの描画モデルに合わせたアニメーションの考え方そのものです。

アニメーションを滑らかにする近道は、
「たくさん計算すること」ではなく「描画のタイミングに合わせること」です。