Webサイトのユーザ体験を向上させる上で、Webページの遷移(ナビゲーション)のパフォーマンスを向上させることは重要です。しかし、ナビゲーションを速くなんて言われても、具体的にどこからどこまでが速ければ良いのかと、悩んだことはないでしょうか。
ユーザーが入力してから、画面上の全ての描画が完了するまでの時間を計測すべきなのか?それとも、ウィンドウ内で見えている部分の描画の速さを計測すべきなのか?議論は尽きません。
さて、今回紹介する「First Paint」も、そんなナビゲーションのパフォーマンス指標値の一つです。First Paintとは、ユーザがブラウザのハイパーリンクやフォームのサブミットボタンを押下した後、ブラウザが最初に描画を開始する時間を指します。ユーザ体験を効果的に改善するための指標として、期待されています。
どのように計測するのか?
HTML5の時代なので、Web標準に含まれているAPIを使いたいところ。しかし残念なことに、First Paintを取得するような機能はWeb標準に存在しません。ただし、IE9+とChromeについては、ベンダ独自仕様としてFirst Paintを取得するAPIを提供しています。
以下は、これらのAPIを利用してFirst Paintを取得する例です。Chromeについてはchromeオブジェクトを介して、IEについてはmsベンダプレフィクス付きでtimingオブジェクトを介してアクセスすることになります。
var getFirstPaint = function() { var firstPaint = 0; // for Google Chrome if (window.chrome && window.chrome.loadTimes) { firstPaint = parseInt(window.chrome.loadTimes().firstPaintTime*1000); // for Microsoft Internet Explorer } else if ( window.performance.timing.msFirstPaint ) { firstPaint = window.performance.timing.msFirstPaint; } return firstPaint; };
上記の例だと、First Paintが開始していない段階でこれらのプロパティを参照すると、常に0を得ることになります。実際の運用ではそこまで困らないはずですが、一応は改善が必要そうです。
また、実際の運用でブラウザ側のパフォーマンスの監視をされている方の中には、performance.timingオブジェクトをそのままBeaconでくるんで監視サーバへ送る人もいます。この方法は、MongoDBのようなドキュメント指向のNoSQLサーバなんかと相性が良いため、やり方を大きく変えたくないところ。バッドプラクティスではありますが、timingオブジェクトを介してWeb標準っぽい方法で取得できるようにできると、運用者としても楽になるでしょう。
こうした課題を解決すべく、ちょっとしたライブラリを開発してみました。やっていることはそんな大層なことではないのですが、Githubにて公開していますのでお試し下さい。
使い方としては、ライブラリをダウンロードし、HTMLドキュメント上から読み込みを行うだけ。
<script src="js/FirstPaintSetter.js"></script>
ChromeやIEの開発者ツールや、JavaScript API経由で、First Paintの値を取得できるようになります。
いやいや、どれぐらい時間がかかったのかが知りたいのだけど?
さきほどの画面キャプチャーを見ていただいたらわかると思うのですが、firstPaintプロパティは今のままだと、なんだかよくわからないマイクロ秒単位の数値が出力されてしまうだけです。ユーザーがアクションを起こしてから何秒後に描画を開始したのか、という時間を計測したいところでしょう。
Web標準には、ナビゲーションを開始した時間を取得するAPIがあります。先ほどから何度か出てきているtimingオブジェクトに含まれる「navigationStart」と呼ばれるプロパティです。ここから取得できる値との差が、ナビゲーションが開始されてから描画されるまでの時間ということになります。先ほどの私が作ったライブラリを活用すると、以下のような方法で取得できるようになります。
var getFirstPaintTime = function() { var time = 0; if( navigation.performance || navigation.performance.timing ) { var navigationStart = navigation.performance.timing.navigationStart; var firstPaint = navigation.performance.timing.firstPaint; if( navigationStart && firstPaint ) { time = firstPaint - navigationStart; } } return time; };
IE9+とChrome以外だと、0を応答するようにしてみました。「いや私はundefinedがいい!」なんて意見もありそうですが、これは実際の運用での決めの問題になりますので、みなさまのお好みに委ねます。
これでは対応できないユースケースが…
実は上記の方法を、私も実際の業務の中でも提案したことがあったのですが、採用は見送らざる得なくなりました。というのはこの方法、遷移前のWebページの作りが、ナビゲーション全体のパフォーマンス劣化を引き起こす場合には、全くと言ってよいほど役に立たないのです。
navigationStartは「prompt to unload a document」というタイミング、すなわちブラウザが次に表示したいWebページの読み込みを開始した時点での時間を記録します。なので、例えば、ユーザーがハイパーリンクをクリックした際に、clickイベントが発火してやたらと重いJavaScriptの処理を実行するケースでは、navigationStartに値を記録するよりも前に、パフォーマンス劣化の原因が潜んでしまうということになります。
こちらがそのサンプルです。→ http://furoshiki.github.io/sample/perl01/
この問題、次期バージョンである「Navigation Timing Level 2
」でも、現段階では改善される様子がありません。「User Timing」あたりで何かしらの代替手段はないかと思いましたが、こちらも厳しそうです。
W3CのWeb Performance WGに正門から堂々と問題提議してみようか、なんて思ったのですが、わりと裏でこっそり議論が始まっていたりすることがあるので、先に関係者に聞いておくことにしました。GoogleのIlya Grigorikに聞くのもいいのですが、彼とはなぜか未だに面識が無いので、以前、シアトルで酒をご一緒させて頂いたTobin Titusにします。彼はWeb標準におけるパフォーマンス系機能のキーパーソンで、現在はマイクロソフトでSr PMとして活動しています。マイクロソフト近辺でパフォーマンスなんてキーワードを出すと「Tobinに聞くといいよ」という感じで丸投げされます。
そんな彼にTwitterで質問してみたところ。
…すごく、当り障りのない答えが。とはいえ、既に改善のために取り組みを進めているようですので、近いうちに何か提案されるのでしょう。機能実装が遅いと言われているマイクロソフトのWebですが、パフォーマンスの機能はかなり早く実装される傾向にありますので、ひょっとしたらProject Spartanが正式リリースされる頃には何かしらの機能が実装されているかもしれません。
今のところ、私が作ったFirstPaintSetter.jsも車輪の再発明な感じがぷんぷんしますが。Tobinの言う新しい機能がWeb標準に登場したら、Polyfillぐらいは実装してみようかと思います。そもそも、近年のパフォーマンス系APIはPerformance Timeline Level 2のインタフェースを継承する流れに入っていて、Navigationもリクエスト単位で分解しようという取り組みが始まっています。なので、timingオブジェクトを利用するという方法もいつまで主流でいられるやら、という感じですが、あくまで今現在の現実的な解決手段にはなりえるかと思います。