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

はじめに

サイトやアプリケーションの要素をペイントすると非常にコストが高くなり、ランタイム パフォーマンスに悪影響を及ぼす可能性があります。この記事では、ブラウザでペイントをトリガーする要因と、不要なペイントが発生しないようにする方法について説明します。

絵画: クイックツアー

ブラウザが実行しなければならない主要なタスクの 1 つは、DOM と CSS を画面上のピクセルに変換することです。変換はかなり複雑なプロセスで行います。まず、マークアップを読み取って、そこから DOM ツリーを作成します。CSS でも同様のことが行われ、CSSOM が作成されます。DOM と CSSOM が結合されて、最終的には、ピクセルの描画を開始できる構造ができあがります。

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

ドームからピクセルへの変換

ここで要点は、Skia のワークロードは、要素に適用したスタイルに直接影響されるということです。アルゴリズムを多用するスタイルを使用している場合は、Skia が行う作業が増えます。Colt McAnlisCSS がページ レンダリングのウェイトに与える影響に関する記事を書いたので、より深く理解する必要があります。

そうは言っても、ペイント処理には時間がかかります。これを減らさなければ、フレーム バジェットの約 16 ミリ秒を超えてしまいます。ユーザーはフレームの欠落に気づき、それがジャンクとして認識され、最終的にアプリのユーザー エクスペリエンスを損なうことになるでしょう。これは本当に望ましくありません。どのような種類のペイント処理が必要になるのか、そしてそれに対して何ができるかを見てみましょう。

スクロール

ブラウザ内で上下にスクロールする際、コンテンツは画面上に表示される前に再ペイントする必要があります。問題がなければ、領域は狭くなりますが、たとえ描画する必要がある要素に複雑なスタイルが適用される場合もあります。ですから、描画する領域が狭いからといって、すぐに描画できるとは限りません。

どの領域が再ペイントされるかを確認するには、Chrome の DevTools の [Show Paint Rectangles] 機能を使用します(右下にある小さな歯車を押すだけです)。DevTools を開いてページを操作すると、Chrome でページの一部をペイントした場所と時間を示す四角形が点滅します。

Chrome DevTools で長方形をペイントする
Chrome DevTools で長方形を描画する

スクロールのパフォーマンスはサイトの成功に不可欠です。サイトまたはアプリケーションのスクロールがうまくいかず、ユーザーはそれを好まないと感じます。そのため Google は、スクロール中にペイント処理を軽く保ち、ユーザーがジャンクを確認しないようにすることに重点を置いています。

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

インタラクション数

インタラクションもペイント処理の原因です。カーソルを合わせる、クリックする、タップする、ドラッグするなどです。たとえばユーザーがカーソルを合わせるたびに、Chrome は対象の要素を再描画する必要があります。また、スクロールと同様に、大規模で複雑なペイントが必要な場合は、フレームレートが低下します。

誰もがきれいで滑らかなインタラクション アニメーションを求めているため、アニメーションで変更したスタイルによって時間がかかりすぎていないかを確認する必要があります。

残念な組み合わせ

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

スクロールしながらマウスを動かすとどうなりますか?スクロール中に要素とうっかり「操作」し、高価なペイントをトリガーすることは十分にあり得ます。そのため、フレーム バジェットの約 16.7 ミリ秒(60 フレーム/秒を達成するためにこの時間内に留まる必要がある時間)を追い越してしまう可能性があります。デモを作成いたしましたので、ご確認いただけますでしょうか。スクロールしてマウスを動かしたときにホバー効果が現れることを願っていますが、Chrome の DevTools でそれがどのように行われるのかを見てみましょう。

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

上の画像では、ブロックの 1 つにカーソルを合わせると、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');
}

ご覧のとおり、本文でクラスを使用して、ホバー効果が「許可」されているかどうかを追跡します。基になるスタイルは、このクラスが存在することを前提とします。

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

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

おわりに

レンダリング パフォーマンスは、アプリケーションを楽しむユーザーにとって非常に重要であり、ペイント ワークロードは常に 16 ミリ秒未満に抑えることを目標とする必要があります。そのためには、開発プロセス全体にわたって DevTools を使用して統合し、ボトルネックが発生したときにそれを特定して修正する必要があります。

特にペイントを多用する要素での意図しない操作はコストが非常に高く、レンダリング パフォーマンスを低下させます。これまで見てきたように、小さなコードを使用して修正できます。

サイトとアプリケーションを確認して、塗装保護を少し施してもらえますか?