[この記事は Mark McDonald、デベロッパー リレーションズ チームによる Geo Developers Blog の記事 "Map Tips: Speeding up page load times with the Google Maps JavaScript API" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]

編集者より: 「Google Maps API のヒント」は、Google Maps API に関してよく寄せられる質問にお答えするものです。今回は、最近よく話題になるウェブのパフォーマンスについて、有益な情報をご提供します。Google のマッピング ツールを導入する方法についての重要なヒントは、Google デベロッパー リレーションズの Mark McDonald の投稿で詳しくご紹介しています。Wikipedia でも同じ処理をしています

ページ上で JavaScript を非同期で読み込むと、パフォーマンスが向上します。Google は JavaScript Maps API のサンプルをすべてアップデートしました。みなさんのサイトも同じように変更できます。アップデートの方法や、動作の概要を知りたい場合は続けてお読みください。サイトのアップデートには何が必要か確認したい場合は、「ブロックを解除するスクリプト」へ移動してください。

JavaScript の読み込みについて

ブラウザのレンダリング プロセスを詳しく説明している、優れたデベロッパー向けリソースはたくさんあります。すべて読むことをお勧めしますが、ここではまず <script> タグがページの読み込み時間に与える影響を見てみましょう。では、少し復習します。
  1. ブラウザがレンダリングするページの HTML コンテンツのストリームを読み込むと、ページの構造とスタイルを表す DOM と CSSOM ツリーが作成されます。
  2. JavaScript の document.createElement()myElement.style.backgroundColor などで DOM とCSSOM の両方を変更できます。
  3. したがって、ブラウザでインラインまたは外部ホスティングされている <script> タグがヒットすると、優先性が宣言されている CSS を取得して処理するまで、それ以降のスクリプトの取得や HTML のレンダリングを停止する必要があります。これはコンテンツをブラウザで初めて表示する際の表示速度に大きく影響します。
詳細については、Ilya Grigorik がこのトピックについて述べている JavaScript の非同期読み込み をご覧ください。

ブロックを解除するスクリプト

私たちはまず、サンプル スクリプトのタグに async 属性と defer 属性を付与しました。

<script src="https://maps.googleapis.com/maps/api/js" async defer></script>

async 属性はブラウザがスクリプトを非同期で取得できるようにするもので、スクリプトは取得するとすぐに実行され、DOM 構築がブロックされず CSSOM 構築の待機時間をなくします。

defer 属性は DOM 構築が完了してからスクリプトを実行するようブラウザに指示するもので、async 属性より古くから存在しており、より汎用的です。async 属性は一部のブラウザ、特に Internet Explorer の古いバージョンではサポートされていないため、defer 属性も含めることにしました。古いブラウザ (Internet Explorer 5.5 まで) では、defer 属性を使用するとページの読み込みが速くなるため、defer 属性を含める価値は充分にあります。ただし新しいブラウザでは async 属性があると defer 属性は無視されます。

WHATWG spec では、動作について以下のように説明されています。

この 2 つの属性を使用すると、 3 種類のモードを選択できます。async 属性のみがある場合、ページ解析が終わり次第すぐにスクリプトが実行され、そのページ以降の解析はブロックされません。async 属性がなく、defer 属性がある場合、ページ解析が完了するとスクリプトが実行されます。どちらの属性もない場合、ユーザー エージェントがページ解析を続行する以前に、すぐにスクリプトを取得し実行します。

次に、マップの初期化コードをウィンドウの onLoad イベントから、コールバックに変更しました。したがって、以下のコードは、
<script src="https://maps.googleapis.com/maps/api/js"></script>
...
<script>
function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng:150.644},
    zoom:8
  });
}
google.maps.event.addDomListener(window, 'load', initMap);
</script>

こうなりました。
<script>
function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng:150.644},
    zoom:8
  });
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap" 
async defer></script>

この変更により、Maps API コードは取得後すぐに実行されるようになったため、マップの初期化コードもすみやかに実行できるようになりました。また <script> タグをカスタム コードの後ろに移動しました。initMap が宣言される前に Maps API コードが読み込まれるといった競合状態を回避するためです。

実装の際に注意すべき点は、コードを呼び出すときに、マップに必要なすべての DOM オブジェクトを使用できる状態にしておくことです。つまり DOMContentLoaded が実行される前のタイミングになります。シンプルなインライン スクリプトでは、ページ内の要素の後ろにスクリプトを置くと問題ありません。

サンプル

次は、Chrome DevTools のウォーターフォールのスクリーンショットで、変更前と変更後の違いがわかるようになっています。1 つ目のスクリーンショットは、async または defer 属性、コールバックを使用していません。上記のコードに加えて、テスト コードは initMap の終わりにある window.performance.now() をログに記録しており、これでマップのカスタマイズを開始するポイントを計算します。

変更前:
DOMContentLoaded が~ 600 ms (下のスクリーンショットでは 658 ms) でトリガーされていて、マップのカスタマイズは 700 ~ 1000 ms あたりで開始できます。
変更後:
DOMContentLoaded は 35 ms でトリガーされ、マップのカスタマイズは 300 ~ 500 ms あたりで開始できます。

コード内の任意の場所で使用できますか?

残念ながら、答えはノーです。スクリプトのタグは決まった順番で同期されて読み込まれないので、google.maps 名前空間を使用するコードはいずれもコールバック内、またはその後に実行する必要があります。

こうした制約を回避するには、API の読み込みを同期に戻すか、コードが実行できるようになり次第、そのコードを親クラスに取り込みます。同期読み込みを選択した場合でも、<script> タグを <body> セクションの末尾へ移動できるため、ページのレンダリングが速くなります。

特に気を付けなければならないのは 次の 2 点です。
  • Google Maps JavaScript API のいずれかをサブクラス化 (カスタム オーバーレイなど) する場合。コールバックの実行後にクラスを定義します。
  • LatLngs がマップの初期化関数以外で定義されている場合。現在、ほとんどの関数で LatLngLiterals が使用できるため、次を渡すだけです。{ lat:33, lng:151 }

コードのアップデート

この最適化は Maps API 以外にも適用できるため、ご自身のサイトもぜひチェックしてみてください。Maps API コードをアップデートした後、外部から読み込まれたスクリプトを探し、定義されたポイントでコードが実行される必要があるかどうかを判断します。必要なければ、コールバックのメカニズムと async 属性と defer 属性を追加します。

有名な JavaScript ライブラリの多くは、async 属性に対応しており、使用を推奨しています。PageSpeed ツールで、対応しているライブラリを確認できます。

みなさんのページの読み込み速度が、少しでも速くなることを願っています。スピードもあなたのウェブサイトの機能の一部なのですから。

Posted by Eiji Kitamura - Developer Relations Team