斉藤 祐也

Webアニメーションを高速化するために知っておくべき10のこと(前編)

アニメーション/トランジションは身の回りに当たり前にあるものです。
むしろ普段の生活では「0」が「1」に変化するものの方が珍しいでしょう。
アニメーション/トランジションはデジタルなWebに対して自然な変化を提供する大切なツールです。
今回はそんなアニメーション/トランジションをより自然にスムーズに動作させるために知っておきたいことを前・後編の2回に分けて紹介していきます。

アニメーションを高速化する理由

アニメーションは先ほど書いたように普段の生活にも存在しています。だからこそ、我々はスムーズではないアニメーションを見つけるのが得意です。

アニメーションに限定した話ではありませんが、FacebookのShane O’Sullivan氏が、ページロード後のレンダリングパフォーマンスが一定でない場合は「いいね!」や「シェア」などのアクション率が低下すると、昨年Londonで開催されたEdge Conferenceで話していました。

edgeconf-performance

特にモバイルデバイスは触って操作するだけあって、ユーザが無意識に自分の感覚と近いものを求めることは当然とも言えるでしょう。

アニメーションの速さを計る「FPS」について

これまでパフォーマンスを計る単位はページロード速度を計るミリ秒が主でしたが、最近ではページロード後のレンダリングやアニメーションのパフォーマンスを計る単位としてフレームレート(単位時間あたりいくつフレームが処理されるかを表す単位)が利用され始めていて、WebにおいてはFPS(Frames Per Second)、つまりフレーム/秒という単位で表すのが一般的です。

ここでいう「フレーム」は映画などで指す「コマ」と同じものと考えて差し支えないでしょう。例えば映画の世界では24FPSが標準的に使われていて、Webにおいては60FPSがたどり着くべき目標とされています。

JavaScriptとCSSではどちらがアニメーションに最適なのか?

Web上でアニメーションを表現するには多くの方法がありますが、ここではJavaScriptとCSSを使った手法にフォーカスします。

CSS3を使ってのアニメーションの以前はJavaScriptによる実装しか選択肢がありませんでした。jQueryの.fadeIn()や.slideUp()などを使ったアニメーションの実装の経験は多くの人が持っているでしょう。

Web開発におけるすべての問題への正しい解答はいつだって「時と場合による」ものですから、どちらが最適かという問いに対する答えも同じです。しかし、本誌のユーザーの体感速度を高めるためのJavaScriptチューニング(後編)にある通り、JavaScriptはシングルスレッドで動作するもので、JavaScriptがアニメーションを行っている間はそれ以外の処理を行うことができません。

JavaScriptは現在のWebサイト/アプリにおいて非常に多くの処理を行う傾向にあるため、CSS3にその処理を代わって実行させることができるようになったことは、パフォーマンス観点では大きな一歩と言えるでしょう。

しかしCSS3のアニメーションを使ったからといって、60FPSの目標を自動的にクリアできるものでは決してありません。

ハードウェア・アクセラレーションは銀の弾丸ではない

CSS3を使ったアニメーション実装が進むにつれ、思った以上にパフォーマンスが出ない場合にハードウェア・アクセラレーションを強制的に利用するテクニックが紹介され始めました。

ブラウザが行う処理はこれまでCPUで行ってきましたが、アニメーションをスムーズに実行するにはGPU(グラフィックス プロセッシング ユニット)を使う方が効率的です。

ハードウェア・アクセラレーションを利用すると言った場合、端的に言ってしまえば、transform: translate3d(0,0,0)あるいは、transform: translateZ(0)という3Dに関連するプロパティを追加すると、実際に値がゼロであってもブラウザはGPUを利用し、そうすることでアニメーションがスムーズに実行される。というように解説されることが多いです。

どんなツール/テクニックにも当てはまることですが、得るものがあれば、必ず失うものもあります。ハードウェア・アクセラレーションを利用することで得られることは、GPUが得意とするグラフィック処理を行うレイヤーを生成(後述)し、そのレイヤー上で処理が完結できること、そして失うものはそのレイヤーを生成し、管理するコストです。

例えば、ハードウェア・アクセラレーションが効果的だからと言って、body * { transform: translateZ(0) } というような記述をすると、本来であればレイヤーが必要ない要素に対してもレイヤーを生成してしまうことになります。これはレイヤーを生成し、管理するためのメモリを余分に費やすことになり、サイト全体で見るとパフォーマンスが低下してしまうことになります。

ハードウェア・アクセラレーションは必要なタイミングで必要な要素にだけ利用するようにするべきです。また、transform: translateZ(0)というような記述でブラウザにハードウェア・アクセラレーションさせるのはあくまでもハックとして考えておいた方がいいでしょう。ブラウザのアップデートとともにハックが利用できなくなるかもしれません。

CSSのプロパティによってアニメーションのパフォーマンスはどう変わるのか

ハードウェア・アクセラレーションはtransform: translateZ(0)だけで利用できるようになるわけではありません。

例えば、ある要素の位置をA地点からB地点に移動させるアニメーションを実装する際に:

  1. transform: translate()を利用する
  2. position:absoluteで要素の位置を指定しtopやleftの値を利用する

というケースが考えられます。

Paul Irish氏はこの2つケースを実装し、両者のパフォーマンスを比べています

Why moving elements with translate   is better than pos abs top left   Paul Irish

結論から言うと、1)のtransform: translate()を利用するほうがパフォーマンスはよくなります。

Paul Irish氏は2)で実装する場合、アニメーションする要素はCPUを利用し、かつ敷かれている背景に対して移動する度に描画を行う必要があるのと比べて、1)の場合、要素はGPUが生成するRenderLayerと呼ばれる専用のレイヤー上を移動し、敷かれている背景などへの影響がないため、スムーズに移動することができると解説しています。

RenderLayerについてはブラウザの内部的な話になってしまうので、ここでは詳しくは触れませんが、2)のposition:absolute + top/leftを使った場合のアニメーションは、ブラウザを白い壁に例えると要素が移動するたびに要素を白く塗りつぶし、移動先に書き直す、というような処理になりますが、1)のtransform: translate()を使った場合は、白い壁に透明のシート(RenderLayer)を被せ、そのシート上に要素を描くことで、移動はシートを動かすだけで実現できるようになる、という様に考えてみるとわかりやすいでしょう。もちろんアニメーションによってはRenderLayerが得意な表現だけを利用できるわけではありませんし、あくまでも現時点のブラウザの仕様に依存する部分も多くあります。

次回後編ではレンダリングプロセス全体について簡単に解説し、アニメーションのボトルネックとなるレイアウトとペイントの2つのプロセスについて、そして、より詳しい計測、デバッグのワークフロー、最後によくあるアクシデントと回避方法について紹介します。

'; js_seriesContent.className = "js_seriesContent"; js_seriesContent.innerHTML = js_seriestitle.innerHTML; js_seriesContent.appendChild(js_serieslist_ul); if ( js_parent.lastChild == js_superior ) { js_parent.appendChild(js_seriesContent); } else { js_parent.insertBefore(js_seriesContent, js_superior.nextSibling); } if (js_serieslist_li_length > 5) { document.getElementsByClassName('moveToSeriesTop')[0].style.display = 'block'; document.getElementsByClassName('moveToSeriesTop')[0].href = document.getElementsByClassName('seriesmeta')[0].getElementsByTagName('a')[0].href; } })(this, this.document); // ソーシャルボタンをクリックされたらgaに送信 var elements, i; elements = document.querySelectorAll('.sns-buttons > li > a.facebook-btn-icon-link'); for (i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function() { ga('send', 'social', 'Facebook', 'like', '/cssradar/2027/'); }, false); } elements = document.querySelectorAll('.sns-buttons > li > a.twitter-btn-icon-link'); for (i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function() { ga('send', 'social', 'Twitter', 'tweet', '/cssradar/2027/'); }, false); } elements = document.querySelectorAll('.sns-buttons > li > a.google-plus-btn-icon'); for (i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function() { ga('send', 'social', 'Google+', '+1', '/cssradar/2027/'); }, false); } elements = document.querySelectorAll('.sns-buttons > li > a.hatena-btn-icon'); for (i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function() { ga('send', 'social', 'Hatebu', 'bookmark', '/cssradar/2027/'); }, false); } elements = document.querySelectorAll('.sns-buttons > li > a.pocket-btn-icon'); for (i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function() { ga('send', 'social', 'Pocket', 'bookmark', '/cssradar/2027/'); }, false); }

週間PVランキング

新着記事

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js Polymer Progressive Web Apps React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket WebVR