不要なペイントを回避する

はじめに

サイトやアプリケーションの要素を描画すると非常にコストがかさみ、実行時のパフォーマンスに悪影響を及ぼす可能性があります。この記事では、ブラウザで描画がトリガーされる原因と、不要なペイントが行われないようにする方法について説明します。

ペインティング: 超簡単なツアー

ブラウザが行う主なタスクの一つは、DOM と CSS を画面上のピクセルに変換することです。この変換は、かなり複雑なプロセスで行われます。最初にマークアップを読み取り、そこから DOM ツリーを作成します。CSS で同様のことを行い、そこから CSSOM を作成します。DOM と CSSOM が結合され、最終的に、ピクセルのペイントを開始できる構造が得られます。

ペイント プロセス自体も興味深いものです。Chrome では、この DOM と CSS を組み合わせたツリーが Skia というソフトウェアによってラスタライズされます。canvas 要素の Skia API を使ったことがあれば、Skia の API に非常になじみがあると思います。moveTo および lineTo スタイルの関数や、より高度な関数が多数あります。基本的に、ペイントする必要があるすべての要素は、実行可能な Skia 呼び出しのコレクションに抽出され、その出力はビットマップの集まりになります。これらのビットマップは GPU にアップロードされ、GPU はこれらのビットマップを合成して最終的な画像を画面に表示します。

Dom からピクセル

重要なのは、Skia のワークロードは、要素に適用するスタイルに直接影響を受けるということです。アルゴリズム的に負荷の高いスタイルを使用している場合、Skia はより多くの処理を行う必要があります。Colt McAnlisCSS がページのレンダリング重量に与える影響に関する記事を執筆していますので、詳細についてはそちらをご覧ください。

とはいえ、ペイント作業には時間がかかるため、減らさないとフレーム バジェットの約 16 ミリ秒を超過してしまいます。ユーザーはフレームを見逃し、ジャンクとして認識され、最終的にアプリのユーザー エクスペリエンスに悪影響を及ぼします。これは本当に望ましくありません。そこで、どのような場合にペイント処理が必要になるのかと、それに対して何ができるかを確認しましょう。

スクロール

ブラウザで上下にスクロールするたびに、コンテンツを再描画してから画面に表示する必要があります。問題がなければ、描画する領域は小さくなりますが、それでも描画する要素に複雑なスタイルが適用される可能性があります。塗装する面積が小さいからといって、すぐに塗り終えられるとは限りません。

再描画される領域を確認するには、Chrome の DevTools で [ペイントの長方形を表示] 機能(右下にある小さな歯車アイコン)を使用します。次に、DevTools を開いてページを操作すると、Chrome がページの一部をペイントした場所と時刻を示す長方形が点滅します。

Chrome DevTools でペイント レクタングルを表示する
Chrome DevTools でペイントの長方形を表示する

スクロールのパフォーマンスは、サイトの成功にとって非常に重要です。サイトやアプリがスムーズにスクロールされないと、ユーザーは不満を感じます。そのため、スクロール中にペイント処理を軽くしてジャンクを発生させないようにすることが Google にとっての利益になります。

以前にスクロール パフォーマンスに関する記事を公開しましたので、スクロール パフォーマンスの詳細についてはそちらをご覧ください。

インタラクション数

ペイント処理の原因となるのは、ホバー、クリック、タップ、ドラッグなどのインタラクションです。ユーザーがこれらの操作(ホバーなど)を行うたびに、Chrome は影響を受ける要素を再描画する必要があります。また、スクロールと同様に、大規模で複雑なペイントが必要な場合は、フレームレートが低下します。

誰もが滑らかで滑らかなインタラクション アニメーションを望んでいるので、ここでも、アニメーションで変化するスタイルによって多くの時間が費やされているかどうかを確認する必要があります。

不幸な組み合わせ

高価な塗料を使用したデモ
高価な塗料を使用したデモ

スクロールとマウスの移動を同時に行うとどうなりますか?スクロール中に要素を誤って「操作」し、コストの高いペイントをトリガーする可能性は十分にあります。その結果、16.7 ms 程度のフレーム バジェット(1 秒あたり 60 フレームを達成するためにこの時間以内に収める必要があります)を超える可能性があります。具体的に説明するデモを作成しました。スクロールしてマウスを動かすと、ホバー効果が適用されるはずです。Chrome のデベロッパー ツールで確認してみましょう。

負荷の高いフレームが表示されている Chrome の DevTools
コストの高いフレームを表示する Chrome の DevTools

上の画像では、いずれかのブロックにカーソルを合わせると、DevTools がペイント処理を登録していることがわかります。デモでは、この点を明確にするために、非常に重いスタイルを使用しています。そのため、フレーム バジェットの上限に達し、場合によっては上限を超えています。無駄にペイント処理を行う必要はまったくありません。特に、他の処理を行う必要があるスクロール中にペイント処理を行う必要はまったくありません。

では、どうすればこのような事態を防ぐことができるでしょうか?修正は非常に簡単に実装できます。ここでのポイントは、ホバー効果を無効にする scroll ハンドラをアタッチし、タイマーを設定してホバー効果を再度有効にすることです。つまり、スクロール時に高コストなインタラクション ペイントを実行する必要がなくなることを保証します。十分な時間停止すると、再びオンに戻しても安全であると判断されます。

以下にコードを示します。

// Used to track the enabling of hover effects
var enableTimer = 0;

/*
 * Listen for a scroll and use that to remove
 * the possibility of hover effects
 */
window.addEventListener('scroll', function() {
  clearTimeout(enableTimer);
  removeHoverClass();

  // enable after 1 second, choose your own value here!
  enableTimer = setTimeout(addHoverClass, 1000);
}, false);

/**
 * Removes the hover class from the body. Hover styles
 * are reliant on this class being present
 */
function removeHoverClass() {
  document.body.classList.remove('hover');
}

/**
 * Adds the hover class to the body. Hover styles
 * are reliant on this class being present
 */
function addHoverClass() {
  document.body.classList.add('hover');
}

ご覧のとおり、body のクラスを使用して、ホバー エフェクトが「許可」されているかどうかを追跡しています。また、基盤となるスタイルはこのクラスの存在に依存しています。

/* Expect the hover class to be on the body
 before doing any hover effects */
.hover .block:hover {
 
}

これですべての設定が完了し、

まとめ

レンダリング パフォーマンスは、ユーザーがアプリを快適に使用するために重要です。ペイントのワークロードは常に 16 ms 未満に抑えるようにしてください。そのためには、開発プロセス全体で DevTools を使用して統合し、ボトルネックが発生したらすぐに特定して修正する必要があります。

特にペイントが多い要素で意図しない操作が行われると、コストが非常に高くなり、レンダリングのパフォーマンスが低下する可能性があります。ご覧のとおり、少量のコードを使用してこの問題を修正できます。

サイトやアプリをチェックして、塗装保護を少し施したほうがよいか確認しましょう。