JavaScript によるインタラクティビティの追加

公開日: 2013 年 12 月 31 日

JavaScript を使うと、ページ内のほぼすべての要素を変更できます。たとえば、コンテンツ その応答に対するレスポンスの責任を 負いますただし、JavaScript は DOM の構築をブロックして、ページのレンダリングを遅らせてしまうことがあります。最適なパフォーマンスを実現するために JavaScript を非同期にして、不要な JavaScript を排除します。 クリティカル レンダリング パスから分離されます。

概要

  • JavaScript では、DOM と CSSOM に対してクエリを実行し、変更することができます。
  • JavaScript の実行は CSSOM でブロックします。
  • JavaScript は、非同期として明示的に宣言されていない限り、DOM の構築をブロックします。

JavaScript はブラウザで実行される動的な言語であり、あらゆる面でページの動作を変更することができます。たとえば、DOM ツリーに要素を追加または削除してコンテンツを変更したり、各要素の CSSOM プロパティを変更したり、ユーザー入力を処理したり、その他にもさまざまな操作が可能です。これを説明するため、前述の「Hello World」サンプルを変更して、短いインライン スクリプトを追加するとどうなるかを見てみましょう。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

試してみる

  • JavaScript を使用すると DOM の内部にアクセスして非表示の span ノード(レンダーツリーに表示されなくても、DOM にあるノード)へのリファレンスを取得することができます。リファレンスを取得すると、テキストの変更(.textContent を利用)や、計算済みの表示スタイルのプロパティを「none」から「inline」にオーバーライドすることができます。サンプルページには「Hello interactive students!」と表示されます。

  • JavaScript では、DOM の要素の新規作成、スタイル設定、追加、削除を行うこともできます。技術的には、ページ全体を単一の大きな JavaScript ファイルにして、1 つずつ要素を作成してスタイル設定を行うことも可能です。ただし、実際には HTML や CSS と連携させる方がはるかに簡単です。この JavaScript 関数の後半では、新しい div 要素を作成し、テキスト コンテンツ、スタイルの設定を行って、body に追加しています。

モバイル デバイスにレンダリングされたページのプレビュー。

これで、既存の DOM ノードのコンテンツと CSS スタイルを変更し、まったく新しいノードをドキュメントに追加しました。このページはデザイン賞を受賞することはありませんが、JavaScript のパワーと柔軟性はおわかりいただけたと思います。

ただし、JavaScript は非常に強力な機能を備えていますが、ページをレンダリングする方法やタイミングに関して多くの制限が加わります。

まず、前の例では、インライン スクリプトがページの下部付近にあることに注目してください。その理由は、実際に試すとわかりますが、このスクリプトを <span> 要素の上に移動するとスクリプトは失敗し、ドキュメントで <span> 要素への参照が見つからないというエラーが出ます。つまり、getElementsByTagName('span')null を返します。これは、重要な特性を示しています。このスクリプトは、ドキュメントに挿入されたそのままの位置で実行されているのです。HTML パーサーがスクリプトタグを見つけると、DOM 構築プロセスが一時停止し、JavaScript エンジンに制御が渡されます。JavaScript エンジンの実行が終了すると、ブラウザは中断したところから再開し、DOM の構築を再開します。

つまり、ページの最後の方にある要素はまだ処理されていないため、スクリプト ブロックでそれらの要素を見つけることはできません。言い換えれば、インライン スクリプトの実行は、DOM 構築をブロックし、結果的に最初のレンダリングが遅れるということです。

ページにスクリプトを導入する際のもう一つの微妙な特性は、DOM だけでなく CSSOM プロパティも読み取って変更できることです。それがまさに、このサンプルで、span 要素の display プロパティを none から inline に変更することによって行っている操作です。最終的な結果は、これで競合状態になりました。

スクリプトを実行したいときに、ブラウザが CSSOM のダウンロードとビルドを完了していない場合はどうすればよいでしょうか。この答えはパフォーマンスにとってあまり良くありません。ブラウザは、CSSOM のダウンロードと構築が完了するまで、スクリプトの実行と DOM 構築を遅らせます

要するに、JavaScript は DOM、CSSOM、JavaScript の実行の間に、多くの新しい依存関係を導入しています。これにより、ブラウザによる画面上のページの処理とレンダリングが大幅に遅れる場合があります。

  • ドキュメント内のスクリプトの位置が重要です。
  • ブラウザがスクリプトタグを見つけると、スクリプトの実行が完了するまで DOM 構築は一時停止します。
  • JavaScript では、DOM と CSSOM のクエリと変更が可能です。
  • JavaScript の実行は、CSSOM の準備が整うまで一時停止します。

「クリティカル レンダリング パスの最適化」とは、主に HTML、CSS、JavaScript の依存グラフを理解して最適化することを意味します。

パーサー ブロックと非同期 JavaScript

JavaScript の実行はデフォルトで「パーサー ブロック」になっています。つまり、ブラウザはドキュメント内のスクリプトを検出すると DOM 構築を一時停止し、JavaScript ランタイムに制御を引き継ぎ、スクリプトを実行させてから DOM 構築を進める必要があります。前の例で、インライン スクリプトを使用してこれを実際に確認しました。実際、インライン スクリプトは、実行を延期する追加のコードを記述しない限り、常にパーサー ブロックになります。

スクリプトタグを使用して組み込まれたスクリプトの場合は、どうでしょうか。前の例では、コードを別のファイルに抽出します。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

試してみる

<script> タグとインライン JavaScript スニペットのどちらを使用しても、動作は同じです。いずれの場合も、ブラウザは一時停止してスクリプトを実行し、その後ドキュメントの残りの部分を処理する必要があります。ただし、外部 JavaScript ファイルの場合、ブラウザはディスク、キャッシュ、またはリモート サーバーからスクリプトが取得されるのを待つために一時停止する必要があります。これにより、クリティカル レンダリング パスに、数万ミリ秒の遅延が加わるおそれがあります。

デフォルトでは、すべての JavaScript はパーサー ブロックです。ブラウザはスクリプトがページで何をしようとしているのかを把握できないため、最悪のシナリオを想定し、パーサーをブロックします。スクリプトが参照されている箇所でスクリプトを実行する必要はないというシグナルをブラウザに伝えることで、ブラウザは DOM の構築を続行でき、準備が整い次第スクリプトを実行できます。たとえば、キャッシュやリモート サーバーからファイルを取得した後などです。

そのためには、async 属性を <script> 要素に追加します。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試してみる

スクリプトタグに async キーワードを追加すると、スクリプトが利用可能になるのを待つ間、ブラウザは DOM 構築をブロックしないよう指示できます。これにより、パフォーマンスが大幅に向上します。

フィードバック