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

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」にオーバーライドすることもできます。これで、ページに「こんにちは、インタラクティブな生徒!」と表示されます。

  • JavaScript では、DOM 内の新しい要素の作成、スタイル設定、追加、削除もできます。技術的には、ページ全体を 1 つの大きな JavaScript ファイルとし、要素を 1 つずつ作成してスタイル設定することもできます。それで問題ありませんが、実際には HTML と CSS を使用する方がはるかに簡単です。JavaScript 関数の 2 番目の部分では、新しい div 要素を作成し、テキスト コンテンツを設定してスタイルを設定し、body に追加します。

ページのプレビュー

以上で、既存の DOM ノードのコンテンツと CSS スタイルを変更し、まったく新しいノードをドキュメントに追加しました。私たちのページはデザイン賞を受賞しませんが、JavaScript がもたらす力と柔軟性は示しています。

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

まず、上記の例では、インライン スクリプトがページの下部付近にあります。その実際に試してみることをおすすめしますが、スクリプトを span 要素より上に移動すると、スクリプトが失敗し、ドキュメント内の span 要素への参照が見つからないというエラーが表示されます。つまり、getElementsByTagName(‘span')null を返します。これは重要な特性を示しています。スクリプトはドキュメントに挿入された正確な時点で実行されます。HTML パーサーは、スクリプト タグを検出すると DOM 構築プロセスを一時停止し、JavaScript エンジンに制御を渡します。JavaScript エンジンの実行が完了すると、ブラウザは中断した箇所から処理を再開し、DOM 構築を再開します。

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

ページにスクリプトを導入する際のもう一つの微妙な特性は、スクリプトが DOM だけでなく CSSOM プロパティも読み取って変更できることです。この例で行ったのは、span 要素の表示プロパティを 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 とマークします。

<!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 構築をブロックしないよう指示できるため、パフォーマンスが大幅に向上します。

フィードバック