ボタンを押したらメニューを開く処理を作ったのに、何も起こらない。
同じJavaScriptでも、ファイルを読み込む場所を変えたら突然動くようになった。
Web制作では、このような問題がよく発生します。
原因の一つが、HTMLとJavaScriptの読み込み順です。JavaScriptが対象の要素より先に実行されると、まだ存在していないボタンやメニューを操作しようとして失敗することがあります。
今回は、ブラウザがHTMLとJavaScriptを読み込む流れや、defer、async、DOMContentLoadedの違いを初心者向けに解説します。
ブラウザはHTMLを上から順番に読み込む
ブラウザは、基本的にHTMLを上から順番に読み取りながらページを組み立てます。
見出し、文章、画像、ボタンなどを見つけるたびに、画面へ表示するための要素として登録していきます。
途中でJavaScriptの読み込み指定を見つけると、そのファイルを取得して処理を実行します。
このとき、JavaScriptより後に書かれているHTMLは、まだ読み取られていない可能性があります。
そのため、ページ下部にあるボタンを操作するJavaScriptが上部で実行されると、「対象となるボタンが見つからない」という状態になることがあります。
JavaScriptは対象の要素が存在してから実行する
JavaScriptでHTMLを操作するには、対象の要素がブラウザに認識されている必要があります。
たとえば、ハンバーガーメニューを開閉する処理では、少なくとも次の要素が必要です。
メニューを開くボタン
開閉するナビゲーション
背景を暗くする要素
閉じるためのボタン
状態を変更する対象
これらがHTMLとして読み込まれる前にJavaScriptが実行されると、処理を設定できません。
見た目は正常に表示されていても、JavaScriptを実行した瞬間には要素が存在していなかった可能性があります。
完成後の画面だけを見るのではなく、どの順番で読み込まれたかを考えることが重要です。
JavaScriptをHTMLの最後で読み込む方法
以前からよく使われている方法が、ページの内容を読み込んだあとにJavaScriptを読み込む構成です。
先にボタンやメニューなどのHTMLを読み込み、その後でJavaScriptを実行するため、対象の要素を見つけやすくなります。
初心者にも仕組みを理解しやすい方法ですが、すべてのJavaScriptを最後へ移動すればよいとは限りません。
サイトの構成やWordPressテーマ、外部サービスによっては、読み込み位置が決められている場合があります。
また、複数のファイルに依存関係がある場合は、配置場所だけでなく読み込む順番も確認する必要があります。
deferはHTMLの解析を止めずに読み込む
deferは、HTMLを読み取る作業と並行してJavaScriptファイルを取得し、HTMLの解析が完了してから実行するための指定です。
JavaScriptの取得中にHTMLの読み込みを長時間止めにくいため、ページ表示への影響を抑えられます。
また、複数のdefer付きファイルがある場合は、基本的にHTMLへ記載された順番を保って実行されます。
そのため、先に共通機能を読み込み、その後でその機能を利用するファイルを実行するといった構成にも対応しやすくなります。
ページ内の要素を操作するJavaScriptでは、deferが使いやすい選択肢になります。
asyncは取得が終わり次第実行する
asyncも、HTMLを読み取りながらJavaScriptファイルを取得するための指定です。
deferとの大きな違いは、ファイルの取得が完了すると、その時点で実行されることです。
複数のasync付きファイルがある場合、HTMLへ書かれた順番どおりに実行されるとは限りません。
容量が小さいファイルや通信状況によって、後に指定したファイルが先に実行される場合があります。
そのため、別のJavaScriptへ依存する処理には注意が必要です。
アクセス解析や広告など、ほかの処理との実行順が重要ではない独立したファイルで使われることがあります。
「表示を速くしたいから、すべてasyncにする」という使い方は避けた方が安全です。
deferとasyncの違い
deferとasyncは、どちらもHTMLの読み込みを妨げにくくするために使用できます。
ただし、実行されるタイミングと順番が異なります。
deferには、次のような特徴があります。
HTMLを読みながらファイルを取得する
HTMLの解析完了後に実行する
複数ファイルの記載順を保ちやすい
ページ内の要素を操作する処理に向いている
asyncには、次のような特徴があります。
HTMLを読みながらファイルを取得する
取得が終わると実行する
複数ファイルの実行順を保証しない
独立して動く処理に向いている
名前が似ているため混同しやすいですが、同じ目的で自由に置き換えられるものではありません。
DOMContentLoadedはHTMLの準備完了を待つ
JavaScript側で、HTMLの解析が完了するまで処理を待つ方法もあります。
そのときに利用されるのが、DOMContentLoadedというイベントです。
このイベントを利用すると、見出しやボタンなど、HTMLの基本的な構造が準備されたあとで処理を開始できます。
JavaScriptを読み込む位置を変更できない場合や、複数の環境で同じファイルを使う場合に役立ちます。
ただし、すでにdeferによってHTML解析後に実行される構成であれば、すべての処理へ機械的に追加する必要はありません。
何を待つための処理なのかを理解して使うことが大切です。
HTMLの準備完了と画像の読み込み完了は違う
DOMContentLoadedは、HTMLの解析完了を知らせるものです。
画像、動画、Webフォントなど、ページ内のすべてのファイルの読み込み完了を待つものではありません。
そのため、画像の表示後の高さや幅を利用する処理では、DOMContentLoadedの時点では必要な情報がそろっていない場合があります。
たとえば、次のような処理では注意が必要です。
画像の高さをもとに位置を計算する
すべての画像表示後にローディング画面を消す
Webフォント適用後の文字幅を測定する
動画の大きさに合わせてレイアウトを変更する
HTMLの要素が必要なのか、画像を含むページ全体の読み込み完了が必要なのかを分けて考えます。
待ち時間を追加するだけでは根本解決にならない
JavaScriptが動かないとき、一定時間待ってから実行する処理を追加すると、一時的に改善することがあります。
しかし、通信速度や端末性能は利用者によって異なります。
自分のパソコンでは十分な待ち時間でも、通信が遅い環境では準備が終わっていない可能性があります。
反対に、高速な環境では不必要に処理を遅らせることになります。
必要なのは「何秒待つか」ではなく、「何の準備が完了したら実行するか」を決めることです。
HTMLの準備、画像の読み込み、外部データの取得など、処理に必要な条件を確認します。
複数のJavaScriptには依存関係がある
Webサイトでは、一つのJavaScriptファイルだけを読み込むとは限りません。
共通機能、スライダー、アニメーション、フォーム、アクセス解析など、複数のファイルを使用することがあります。
あるファイルが、別のファイルで用意された機能を使用している場合は、実行順が重要です。
必要なファイルより先に処理が実行されると、機能が存在しないというエラーが発生します。
制作中は正常でも、本番環境の高速化機能によってファイルの結合や遅延読み込みが行われ、順番が変わることもあります。
高速化設定後は、見た目だけでなくJavaScriptを使用する機能も確認する必要があります。
WordPressでは正しい方法でファイルを読み込む
WordPressテーマでは、テンプレートへ読み込み指定を直接書くこともできます。
しかし、WordPressにはJavaScriptやCSSを登録し、適切な位置で読み込むための仕組みがあります。
この仕組みを利用すると、次のような情報を管理しやすくなります。
読み込むファイルの場所
必要な別ファイル
ファイルのバージョン
ヘッダーとフッターのどちらで読み込むか
同じファイルの重複読み込み
プラグインとの依存関係
テーマやプラグインが同じ機能を別々に読み込むと、不具合の原因になることがあります。
WordPressでは、単に画面へ表示できるかだけでなく、ほかの機能と共存できる方法で読み込むことが重要です。
動的に追加した要素は後から現れる
ページを開いた時点では存在せず、JavaScriptによって後から追加される要素もあります。
検索結果の追加表示、モーダル内の内容、絞り込み後の商品、外部データから作成した一覧などが代表的です。
ページを最初に読み込んだとき、その要素はまだ存在していません。
そのため、最初の処理でクリック操作を設定しようとしても対象を見つけられない場合があります。
この問題は、HTMLの読み込み完了を待つだけでは解決しません。
要素を追加したあとに処理を設定するか、後から追加される要素にも対応できるイベントの管理方法を使用します。
一部のページだけでエラーになる場合もある
同じJavaScriptをサイト内の全ページで読み込んでいる場合、対象の要素が存在しないページもあります。
トップページにはスライダーがあるものの、会社概要ページにはないといったケースです。
対象が存在しない状態で処理を続けると、途中でエラーが発生し、その後に書かれた別の機能まで動かなくなることがあります。
実行前に対象の要素が存在するかを確認し、存在するページでだけ処理する構成にします。
一つの機能のエラーが、メニューやフォームなど別の機能へ影響していないかも確認が必要です。
開発者ツールでエラーを確認する
JavaScriptが動かないときは、何度もクリックするだけでなく、ブラウザの開発者ツールを確認します。
コンソールには、エラーが発生したファイルや行番号、問題の種類などが表示されることがあります。
また、ネットワークの確認画面では、JavaScriptファイル自体を取得できているかを調べられます。
主に次の点を確認します。
JavaScriptファイルが読み込まれているか
ファイルの場所が間違っていないか
対象の要素を取得できているか
必要な機能が先に読み込まれているか
途中でエラーが発生していないか
同じファイルを複数回読み込んでいないか
高速化機能によって順番が変わっていないか
エラーメッセージを確認せずに処理を書き直すと、原因とは関係のない部分まで変更してしまう可能性があります。
JavaScriptが動かないときの確認順序
読み込み順が疑われる場合は、次の順番で確認すると原因を整理しやすくなります。
JavaScriptファイルが正常に取得されているか
コンソールにエラーが表示されていないか
対象のHTML要素が存在するか
JavaScriptの実行時点で要素が準備されているか
deferやasyncの指定が適切か
複数ファイルの読み込み順が正しいか
対象が後から追加される要素ではないか
特定のページだけに存在する要素ではないか
WordPressや高速化機能が順番を変更していないか
キャッシュに古いファイルが残っていないか
一度に複数の設定を変更せず、どの段階で処理が止まっているのかを確認します。
まとめ
JavaScriptでHTMLを操作するには、対象の要素がブラウザに認識されたあとで処理を実行する必要があります。
ブラウザはHTMLを上から順番に読み込むため、JavaScriptの位置や実行方法によっては、まだ存在しない要素を操作しようとして失敗します。
deferはHTMLの解析完了後に順番を保って実行しやすく、asyncは取得が完了したファイルから実行されます。
DOMContentLoadedを使えばHTMLの準備完了を待てますが、画像や外部データ、後から追加される要素には別の考え方が必要です。
動かないときに待ち時間を増やしたり、読み込み位置を何となく変更したりするのではなく、必要な要素や機能がいつ準備されるのかを確認する。
この考え方を身につけることで、JavaScriptの不具合を効率よく切り分け、環境が変わっても安定して動くWebサイトを作れるようになります。