JavaScript超入門⑦ DOMとクリックイベントを使おう
querySelector、textContent、addEventListenerを使い、ボタンを押すと表示が変わるWebページを作ります。
ここまでは主にコンソールで結果を見てきました。そろそろ「ページのボタンを押したら、文章が変わる」ものを作ってみたいですよね。
今回はDOMを通してHTMLの要素を選び、イベントでクリックへ反応させます。JavaScriptの関数を「今呼ぶ」のではなく、「操作されたら呼んでもらう」ことがポイントです。
1. DOMは、ブラウザが持つページの構造
DOM(Document Object Model)は、HTMLなどの文書をプログラムから扱うための仕組みです。ブラウザは読み込んだHTMLから、要素が親子関係でつながる構造を作ります。
例えば次のHTMLなら、bodyの中に段落とボタンがあります。
<body>
<p id="message">ボタンを押してください</p>
<button id="changeButton" type="button">変更する</button>
</body>
JavaScriptはこの段落やボタンをオブジェクトとして取り出し、中身を変えられます。
HTMLファイルそのものを保存し直す処理ではありません。今ブラウザにある文書の状態を変えるため、通常は再読み込みすると元のHTMLへ戻ります。
2. まずは、動くページを1つ用意する
次の全体をmessage.htmlとしてUTF-8で保存し、ブラウザで開いてください。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ボタンで文章を変える</title>
</head>
<body>
<h1>メッセージの練習</h1>
<p id="message" aria-live="polite">ボタンを押してください</p>
<button id="changeButton" type="button">変更する</button>
<script>
const message = document.querySelector("#message");
const button = document.querySelector("#changeButton");
button.addEventListener("click", () => {
message.textContent = "クリックされました";
});
</script>
</body>
</html>
「変更する」を押すと、段落が「クリックされました」へ変わります。動いたら、次の章から中身を分解しましょう。
type="button"は普通の操作ボタンであることを明示します。フォーム内へ移したとき、意図せず送信するボタンになるのを避けられます。
aria-live="polite"は、文章が変わったことを支援技術へ穏やかに知らせるための指定です。マウスの見た目だけでなく、読み上げで使う場合も考えています。
3. querySelectorで対象を見つける
完成コードの最初の2行です。
const message = document.querySelector("#message");
const button = document.querySelector("#changeButton");
documentは今のページの文書を表すオブジェクトです。querySelectorはCSSのセレクターで対象を探します。
セレクターは「どの要素を選ぶか」の書き方。#messageはidがmessageの要素です。
| セレクター | 選ぶ対象 |
|---|---|
#message |
idがmessage |
.notice |
classにnoticeを持つ |
button |
button要素 |
querySelectorが返すのは、最初に一致した1要素です。一致がなければnullになります。MDNのquerySelectorで戻り値を確認できます。
idはHTMLとJavaScriptで一致させる
HTMLでid="message"なのにJavaScriptで"#messages"と書くと見つかりません。大文字・小文字、末尾のs、先頭の#を確認します。
1ページのidは重複させないようにします。同じidのボタンをコピーして増やすと、思った方を選べなくなる原因になります。
4. textContentは、文字として中身を変える
message.textContent = "文字を変更しました";
これは、選んだ要素の文字内容へ新しい文字列を設定する操作です。この1行をscript内で直接実行すれば、クリックを待たずに変更されます。
コンソールのconsole.logと違い、ページ上の段落が変わります。
文字だけを出すならinnerHTMLは使わない
textContentに"<strong>こんにちは</strong>"を入れた場合、その記号も文章として表示されます。HTMLタグとして解釈する処理ではありません。
利用者が入力した文章を表示するとき、安易にinnerHTMLへ入れるとHTMLとして解釈され、攻撃につながる場合があります。今回は文字を表示するだけなのでtextContentを使います。MDNのtextContentの説明にも違いがあります。
なお、段落に子要素が入っていると、textContentの設定でその内容も置き換わります。「文字だけを変える場所」を専用のspanなどに分ける設計も役立ちます。
5. addEventListenerは、後で動く処理を登録する
button.addEventListener("click", () => {
message.textContent = "クリックされました";
});
クリックや文字入力など、ブラウザで起こる出来事がイベントです。addEventListenerは、その出来事が起きたときに呼ぶ関数を登録します。
button:どの要素で"click":何が起きたら() => { ... }:どの処理を行うか
この() =>は、前の関数の回で学んだアロー関数です。名前を付けず、その場で渡しています。
登録した瞬間には、まだ本文を変更しない
ページを開いたときの処理は、イベントの登録までです。クリックするまでは、渡した関数の中を実行しません。
- HTMLを読む
- 段落とボタンを探す
- クリック時の関数を登録する
- 利用者がクリックする
- 登録した関数が呼ばれ、文章が変わる
「上から順に書いたのに、最後の行がまだ動かない」のではなく、最後の関数は未来のイベント用に渡しているのです。
名前付き関数を渡すなら、かっこを付けない
function changeMessage() {
message.textContent = "クリックされました";
}
button.addEventListener("click", changeMessage);
これは完成コードの登録部分と置き換えて使う別の書き方です。両方を追加する必要はありません。
changeMessage()を渡すと、その場で呼び出してしまいます。後で呼んでほしいので、関数そのものを表すchangeMessageを渡します。MDNのaddEventListenerでも、リスナーとして関数を渡す形が説明されています。
6. scriptを要素より後ろに置く理由
ブラウザは通常、HTMLを読み進めながら要素を作ります。要素より先にscriptを実行すると、まだ段落やボタンがなく、querySelectorがnullを返すことがあります。
今回の完成コードは、操作する要素の後にscriptを置いています。
外部ファイルに分ける場合は、例えば<script src="app.js" defer></script>という方法もあります。deferは外部の通常スクリプトをHTMLの解析後に実行させる指定で、インラインscriptへ付ければ同じように働くわけではありません。まずは1ファイル版で流れを理解しましょう。
7. ボタンが反応しないときの見方
| 症状 | 確認すること |
|---|---|
| nullのプロパティを読めない | idのつづり、#、scriptの位置 |
| 開いた直後に文章が変わる | 関数を登録せず、その場で呼んでいないか |
| 1回押すと複数回動く | 同じ処理を重複登録していないか |
| 再読み込みすると戻る | 今回は保存機能がなく、正常な動作 |
| HTMLへコードが見えてしまう | scriptタグの内側へ書いたか |
まずコンソールで最初のエラーを見ます。console.log(button);で、要素が取れているか確認する方法もあります。
ボタンはマウスだけでなく、Tabで移動してEnterやSpaceでも試してください。クリック可能な見た目のdivを作るより、HTMLのbuttonを使う方が、標準の操作を利用できます。
8. 練習:「元に戻す」ボタンを追加しよう
2つ目のボタンを作り、押すと最初の文章へ戻るようにしてください。
模範解答
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>メッセージを戻す</title>
</head>
<body>
<h1>メッセージの練習</h1>
<p id="message" aria-live="polite">ボタンを押してください</p>
<button id="changeButton" type="button">変更する</button>
<button id="resetButton" type="button">元に戻す</button>
<script>
const message = document.querySelector("#message");
const changeButton = document.querySelector("#changeButton");
const resetButton = document.querySelector("#resetButton");
changeButton.addEventListener("click", () => {
message.textContent = "クリックされました";
});
resetButton.addEventListener("click", () => {
message.textContent = "ボタンを押してください";
});
</script>
</body>
</html>
解説
新しいボタンへresetButtonという別のidを付け、querySelectorで取得します。リセットは段落の内容を初期文字列へ代入するだけなので、最初に変更ボタンを押していなくても問題ありません。
確認は「変更 → 元に戻す → もう一度変更」の順で行います。期待する文章へ毎回変われば成功です。ページ全体を再読み込みするリセットではなく、指定した段落だけを戻しています。
次の一歩
「要素を選ぶ → 操作を待つ → 値を変更する」が、Webページに動きを付ける基本です。次は変数で現在の数を覚え、カウンターを作ります。