reflowとrepaintから見るjQueryパフォーマンス問題

jQueryが遅いと言われる原因の本体は「reflow」と「repaint」

jQueryのパフォーマンス問題の多くは、jQueryのアルゴリズムが遅いからではありません。
ブラウザの描画処理を大量に発生させやすい書き方になることが本質です。

そして、その描画処理の正体が「reflow」と「repaint」です。

jQueryを使っていると「ちょっとしたDOM操作のはずなのに重い」という現象に遭遇します。
そのとき実際に遅くなっているのはJavaScriptではなく、ブラウザのレンダリングエンジンです。

reflowとは何か

reflowは「レイアウト再計算」です。
ブラウザはHTMLとCSSから、画面上の各要素の位置とサイズを計算しています。

例えば以下のようなDOMがあります。

  • divの幅
  • テキストの折り返し
  • 画像の高さ
  • 親要素のサイズ

これらはすべて相互に依存しています。
1つの要素のサイズが変わると、周囲のレイアウトも変わります。

つまり、次のような処理をするとreflowが発生します。

$("#box").width(300);

幅を変えた結果、

  • 改行位置が変わる
  • 親の高さが変わる
  • 下の要素の位置がずれる

ブラウザはページ全体のレイアウトを再計算します。
これがreflowです。

reflowは最もコストの高い処理です。

repaintとは何か

repaintは「再描画」です。
位置やサイズは変えず、見た目だけが変わる場合に発生します。

$("#box").css("color", "red");

文字色が変わるだけなら、レイアウトは変わりません。
しかしピクセルの描き直しが必要です。

これがrepaintです。

repaintはreflowより軽いですが、それでも無料ではありません。
特にモバイルでは無視できない負荷になります。

どの操作がreflowを引き起こすのか

代表的なreflow発生操作は次の通りです。

操作 内容
width/height変更 要素サイズ変更
padding/margin レイアウト変化
font-size変更 文字配置変化
display変更 要素の存在状態変化
DOM追加・削除 構造変化

jQueryのコードでよく見かけるものはすべて該当します。

$(".item").show();

これはdisplay:none → blockになるため、reflowが発生します。

$(".item").append("<li>new</li>");

DOM構造が変わるため、やはりreflowです。

jQueryで遅くなる典型パターン

特に危険なのが「読み取りと書き込みの交互実行」です。

$(".item").each(function(){
  const h = $(this).height();
  $(this).height(h + 10);
});

このコードは一見問題なさそうですが、内部では次の流れになります。

1 要素の高さ取得(レイアウト情報要求)
2 ブラウザがreflowを実行
3 高さ変更
4 再びreflow

これが要素数分繰り返されます。
10要素なら10回、100要素なら100回reflowです。

これをレイアウトスラッシングと呼びます。

なぜjQueryで起きやすいのか

jQueryが特別遅いのではありません。
jQueryはDOMを「簡単に触れる」ため、気づかずreflowを多発させやすいのです。

例えば次のコード。

$(".box").css("width", "100px");
$(".box").css("height", "100px");
$(".box").css("margin", "10px");

3回のスタイル変更は、3回のレイアウト再計算を誘発する可能性があります。

ネイティブでまとめると違います。

const el = document.querySelector(".box");
el.style.cssText = "width:100px;height:100px;margin:10px;";

レイアウト計算は1回です。

アニメーションが重くなる理由

jQueryのアニメーションがカクつく原因もここにあります。

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

leftプロパティはレイアウトに影響します。
つまり1フレームごとにreflowが起きます。

60fpsのアニメーションなら、1秒間に60回reflowです。
これはCPU負荷が非常に高くなります。

一方、transformなら違います。

el.style.transform = "translateX(200px)";

これはレイアウトを変えません。
GPUコンポジット処理になり、reflowが発生しません。

実際に起きる問題

reflow過多になると次の症状が出ます。

  • スクロールがカクつく
  • 入力が遅れる
  • アニメーションが止まる
  • モバイルで固まる

特にスマートフォンでは顕著です。
PCで正常でも本番で遅くなる原因の多くがこれです。

対策の基本

重要なのは「DOMアクセスをまとめる」ことです。

悪い例:

for(let i=0;i<100;i++){
  $(".list").append("<li>"+i+"</li>");
}

100回reflowします。

改善例:

let html="";
for(let i=0;i<100;i++){
  html += "<li>"+i+"</li>";
}
$(".list").html(html);

reflowは1回です。

最後に

jQueryのパフォーマンス問題の正体は、jQueryの速度ではありません。
ブラウザ描画モデルを意識せずにDOMを書き換えてしまうことです。

jQueryはDOM操作を簡単にしましたが、同時に「画面を作り直している」という事実を見えにくくしました。
そして現代のWebアプリでは、この見えないコストがパフォーマンスを支配します。

jQueryを最適化するというより、
「DOMは描画エンジンを動かしている」という視点を持つことが、最も効果のある対策になります。