不必要な描画を避ける方法

HTML5 Rocks

はじめに

Web サイトやアプリケーションにおいて、要素の描画はとても重く、場合によっては実行時のパフォーマンスに致命的な影響を及ぼします。この記事では、ブラウザにおいて描画が何によって引き起こされているのか、そして、どうすれば不要な描画を避けられるのかについて、ざっと見ていきましょう。

よくわかる描画の仕組み

ブラウザの重要なタスクの一つはDOMとCSSを画面のピクセルに変換することですが、そこではかなり複雑な処理が行われています。まずはマークアップを解釈し、DOMツリーを作成するところから始まります。またCSSも同様に解釈され、CSSOMが作成されます。その後、DOMとCSSOMが結合され、ピクセルを描画する準備が整います。

ブラウザの仕組みをもっと理解したいならこちらの記事も見てみてください!

描画処理はそれ自体非常に興味深いものです。Chrome では、結合されたDOMとCSSは Skiaというソフトウエアによってラスタライズされます。canvas要素を使った事がある方は、SkiaのAPIに見覚えがあると思います。moveTo- や lineTo-ではじまる関数や、もっと高度なものもたくさんあります。基本的に、描画されるすべての要素は、実行可能なSkia APIの呼び出しに集約され、多数のビットマップが出力されます。これらのビットマップはGPUにアップロードされ、合成されることにより、スクリーン上に最終的な絵が表示されます。

ここで重要な事は、要素に適用するスタイルが、Skiaの処理負荷に直接影響することです。アゴリズム的に重いスタイルを使用した場合は、Skiaの作業量も増えます。詳しくはColt McAnlisCSSが及ぼす画面描画の処理負荷についての記事を読んでみてください。

これを踏まえると、描画処理はパフォーマンスに影響を与え、処理を減らさなければ1フレームの処理時間の制限である ~16msを超えてしまいます。そうなるとユーザはコマ落ちに気づきストレスを感じます。ひいては、自分のアプリのユーザ体験を損なうことになります。そのような事を望まないのであれば、描画が発生する原因を理解して、何が出来るかを考えましょう。

スクロール

ページが上下にスクロールされると、ブラウザは画面上に現れる前にコンテンツを再描画する必要があります。それが小さい範囲なら問題にならないかもしれませんが、描画される要素には複雑なスタイルが適用されているかもしれません。そのため、小さい範囲だとしてもすぐに描画されるわけではありません。

どのエリアが再描画されているのかを Chrome のデベロッパーツールの “Show Paint Rectangles” (右下の小さな歯車アイコンをクリック)という機能で調べることができます。デベロッパーツールを開いたままページを操作すると Chrome が描画した部分がそのタイミングで長方形で囲まれます。

Chrome のデベロッパーツールの Show Paint Rectangles

スクロールパフォーマンスはあなたのウェブサイトの成功に必要不可欠です。ユーザはウェブサイトやアプリケーションがうまくスクロールしないことに必ず気づき、そしてそれを嫌がります。ユーザがストレスを感じないために、私達はスクロール中の描画処理を軽くすることに注意を払わないといけません。

私は以前スクロールパフォーマンスについての記事を書いたので、詳細について更に知りたい場合は参照してください。

インタラクション

ホバー、クリック、タッチ、ドラッグなどのインタラクションもまた、描画が発生する原因となります。ユーザーがそのようなインタラクションを行った場合、Chromeは影響のある要素を再描画します。そして、スクロールと同様に、広範囲で複雑な描画が発生した場合、フレームレートが落ちるでしょう。

ユーザーは素晴らしくなめらかでインタラクティブなアニメーションを求めているので、ここでも我々はアニメーションにおけるスタイルの変更に時間がかかりすぎていないか確認する必要があるでしょう。

残念な組み合わせの一例

コストがかかる描画のデモ

スクロールしながら同時にマウスを動かしてしまった場合、どうなるでしょうか。私にとっては、スクロールして通過する要素をうっかりと「触って」しまい、コストのかかる描画処理を開始させてしまうことは非常に良くあります。すると、そのような描画処理のせいで 16.7ms 以内というフレーム制約を超えてしまうこともありえます (60fps を実現するためにはこの 16.7ms という時間以内に収まるようにしなければなりません。) 。何が言いたいかを正確に説明するために、デモを作成しました。スクロールしながらマウスを動かすと、hover エフェクトが割り込むのが見えるでしょう。では、Chrome DevTools により何が得られるか見てみましょう。

Chrome DevToolsによるコストがかかるフレームの表示

上の画像を見ると、ブロック要素の上に hover するたびに DevTools が描画処理を登録しているのが分かると思います。デモではわかりやすくするために非常に重いスタイルを描画させていたので、ときどきフレーム制約を超えています。他に処理しなければならないことがある場合などは特に、このような描画処理がスクロール中に無駄に発生することはどうしても避けたいです。

では、どのようにすれば避けることができるのでしょうか。じつは修正は非常に簡単です。ここで紹介する方法は、 hover エフェクトを無効にし、再び有効にするタイマーをセットするような scroll ハンドラをセットするというものです。こうすれば、スクロールしているときはコストのかかるインタラクションによる描画処理をする必要がないということを保証できます。十分の時間スクロールをしないと、 hover エフェクトを再び有効にしても問題ないと判断します。

このような変更は、アプリケーションのユーザエクスペリエンスに影響を与えますので、慎重に判断してください。 hover エフェクトが再度有効になるまでの遅延をどの程度許容するのか判断できるのは、あなたか、あなたのチームだけです。

コードはこちら:

// 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');
}

ご覧のとおり、 hover エフェクトが「許可されているかどうか」を追跡するため、 body にある class をつけ、下位のスタイルはこのクラスが存在するかどうかに依存しています。

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

以上、たったこれだけのことです。

まとめ

アプリケーションの描画パフォーマンスはユーザにとって非常に重要なので、描画の処理時間が常に16秒以下になるように努めなければなりません。そのためには、開発の間中ずっと DevTools を立ち上げておき、ボトルネックを特定し、その都度修正するべきです。

特に、描画に時間がかかる要素に対して意図しないインタラクションが発生した場合、描画が重くなりパフォーマンスが低下する可能性があります。これまで見てきたように、これは小さな修正で回避可能です。

あなたのサイトやアプリケーションを注意深く見てみてください。もう少し描画を減らしてみてもいいのではないでしょうか。

Comments

0