DOMとイベント
JavaScriptを使ってHTMLの要素を制御する
今回のゴール
- JavaScriptを使ってHTML要素を取得・操作する方法を学ぶ
- イベントを使って、ユーザーの操作を検知する方法を学ぶ
DOMとは (1)
DOM(Document Object Model) は、HTMLを JavaScript から操作するための仕組みです。
ブラウザは HTML を読み込むと、その構造をツリー状のデータ構造 (ツリー構造) として内部で管理します。このツリー構造を DOM と呼びます。
(ツリー構造は親子関係のあるデータを表すもので、PC上のフォルダやファイルなどもツリー構造と言えます)
JavaScript では、この DOM を通じて HTML 要素を取得したり、変更したりできます。
DOMとは (2)
例えば、以下のような HTML があるとします。
<body>
<div id="content">
<h1>タイトル</h1>
<p>本文です</p>
</div>
</body>
ブラウザは、これを以下のようなツリー構造 (DOM) として認識します。
body
└── div (id="content")
├── h1
│ └── "タイトル"
└── p
└── "本文です"
DOMとは (3)
DOM の各要素のことを ノード と呼びます。
| 用語 |
説明 |
| 親ノード |
ある要素を含んでいる要素(上位の要素) |
| 子ノード |
ある要素の中に直接含まれている要素 |
| 兄弟ノード |
同じ親を持つ要素同士 |
親ノード
├── 子ノード
└── 子ノード (兄弟ノード)
JavaScript では、DOM を操作することで HTML の内容を動的に変更できます。
要素の選択: document.querySelector (1)
HTML 要素を JavaScript で操作するには、まず要素を取得する必要があります。
document.querySelector() は、CSS のセレクタを使って要素を 1 つ取得するメソッドです。
// <h1> 要素を取得する
const heading = document.querySelector("h1");
// id="main" の要素を取得する
const main = document.querySelector("#main");
// class="button" の要素を取得する(最初の1つだけ)
const button = document.querySelector(".button");
要素の選択: document.querySelector (2)
取得した要素に対して、プロパティやメソッドを使って操作できます。
<h1 id="title">こんにちは</h1>
// id="title" の要素を取得
const title = document.querySelector("#title");
// テキストの内容を変更
title.textContent = "ようこそ!";
// 結果: <h1 id="title">ようこそ!</h1>
要素の選択: document.querySelector (3)
セレクタに一致する要素がない場合、null という値が返されます。
// 存在しない要素を取得しようとする
const notFound = document.querySelector("#not-exist");
console.log(notFound); // null
// null に対してメソッドを呼び出すとエラーになる
notFound.textContent = "テスト"; // エラー!
要素を操作する前に、取得できたかどうか確認することが大切です。
要素の選択: document.querySelectorAll (1)
document.querySelectorAll() は、セレクタに一致するすべての要素を取得します。
<ul>
<li class="item">りんご</li>
<li class="item">みかん</li>
<li class="item">バナナ</li>
</ul>
// class="item" のすべての要素を取得
const items = document.querySelectorAll(".item");
console.log(items.length); // 3
要素の選択: document.querySelectorAll (2)
querySelectorAll() で取得した要素は、for...of を使って一つ一つの要素に対する処理を行うことができます。
const items = document.querySelectorAll(".item");
// すべての項目のテキストを表示
for (const item of items) {
console.log(item.textContent);
}
// "りんご"
// "みかん"
// "バナナ"
要素が持っているプロパティ・メソッドを調べる (1)
DOM の要素は、継承という仕組みで共通の機能を持っています。
Node (すべてのノードの基本)
└── Element (すべての要素の基本)
└── HTMLElement (すべてのHTML要素の基本)
├── HTMLParagraphElement (<p>)
├── HTMLDivElement (<div>)
├── HTMLInputElement (<input>)
└── ... その他の要素
例えば p要素であれば HTMLParagraphElement となりますが、すべてのHTMLの要素は HTMLElement を継承しているため、HTMLElement, Node, Element のプロパティ・メソッドもすべて持っており、これらのwebページからプロパティ・メソッドを調べることができます。
(HTMLElementを継承する要素の一覧はこちらを参照ください)
補足: なぜ Node, Element, HTMLElement と分かれているのか?
"Node を継承するが Element は継承しない", "Element は継承するが HTMLElement は継承しない" ものが存在するためです。
- 例えば、
document は Node を継承しますが、Element は継承していません
- また、
<svg> 要素 (SVG画像に使われる要素) は Element は継承しますが、HTMLElement は継承しません
要素が持っているプロパティ・メソッドを調べる (2)
DOM 要素がどのようなプロパティやメソッドを持っているか、開発者ツールで調べることもできます。
- ブラウザで開発者ツールを開く(F12 または右クリック → 検証)
- コンソールタブを開く
document.querySelector() で要素を取得 (Chrome の場合、 $() という書き方でもできます)
- 取得した要素をコンソールに表示してメソッド・プロパティを確認する
(デモ)
DOM のプロパティ・メソッドを使って要素を操作する
以降の "(参考)" と書いているスライドで、DOMを操作できるプロパティやメソッドを簡単に紹介していきます。
DOMのプロパティ・メソッドは膨大であり、これらのプロパティ・メソッドを最初から一つ一つ覚える必要はなく、必要に応じて調べていけば大丈夫です。ただ、ざっくりと「DOMに対してどういう操作ができるのか」を知っていることで、よりスムーズに調べられるようにできることを目指します。
(参考) 要素の追加: document.createElement(), appendChild()
特定の要素の中に追加することもできます。
<div id="container"></div>
const container = document.querySelector("#container");
// 新しいボタンを作成
const button = document.createElement("button");
button.textContent = "クリック";
// container の中に追加
container.appendChild(button);
// 結果: <div id="container"><button>クリック</button></div>
(デモ)
(参考) スタイルの操作: element.style
JavaScript から要素のスタイルを直接変更できます。
const box = document.querySelector("#box");
// スタイルを変更
box.style.backgroundColor = "blue";
box.style.color = "white";
box.style.padding = "20px";
CSS のプロパティ名は、JavaScriptではキャメルケース (複数の文字に対し、2番目以降の文字の先頭を大文字にする書き方) で書きます
| CSS |
JavaScript |
background-color |
backgroundColor |
font-size |
fontSize |
(参考) 属性の操作: setAttribute()
要素の属性(id, class, src など)を JavaScript で変更できます。
const image = document.querySelector("img");
// src 属性を変更
image.setAttribute("src", "new-image.jpg");
// alt 属性を変更
image.setAttribute("alt", "新しい画像");
// class 属性を追加
image.setAttribute("class", "thumbnail");
要素に対するイベントを取得する
イベントとは、ユーザーの操作(クリック、キー入力など)やブラウザの動作(ページ読み込み完了など)のことです。
addEventListener() を使って、イベントが発生したときに実行する処理を登録し、ユーザーの操作に反応する処理が記述できます。
const button = document.querySelector("button");
function handleClick() {
console.log("ボタンがクリックされました");
}
// クリックされたときの処理を登録
button.addEventListener("click", handleClick);
addEventListener() の詳細
addEventListener() には 2 つの引数を渡します。
element.addEventListener("イベント名", 実行する関数);
| 引数 |
説明 |
| イベント名 |
"click", "keydown", "submit" など |
| 実行する関数 |
イベント発生時に呼び出される関数 |
「その要素がどのイベントを使えるか?」については、プロパティ・メソッドと同様に Element や HTMLElement のページ、その要素自体のページから確認してみてください (なお、click や keydown などマウスやキーボードのイベントは Element から来ています)
(参考) 複数のイベントを割り当てる
同じ要素に対して、複数のイベントリスナーを登録できます。
const button = document.querySelector("button");
function handleClick1() {
console.log("処理1: ログを出力");
}
function handleClick2() {
console.log("処理2: 別のログを出力");
}
// 1つ目のリスナー
button.addEventListener("click", handleClick1);
// 2つ目のリスナー
button.addEventListener("click", handleClick2);
// => クリックされると両方とも実行される
(参考) removeEventListener()
登録したイベントリスナーを削除することもできます。
const button = document.querySelector("button");
// 名前付き関数を定義
function handleClick() {
console.log("クリックされました");
}
// イベントリスナーを登録
button.addEventListener("click", handleClick);
// イベントリスナーを削除
button.removeEventListener("click", handleClick);
注意: 削除するには、登録時と同じ関数を指定する必要があります。
イベントオブジェクト (1)
イベントが発生すると、イベントオブジェクトが自動的に関数に渡されます。
const button = document.querySelector("button");
function handleClick(event) {
// event にはイベントの詳細情報が入っている
console.log(event);
}
button.addEventListener("click", handleClick);
※ 上記では event という引数にイベントオブジェクトを入れていますが、「関数の最初の引数にイベントオブジェクトが入ってくる」というだけなので、引数の名前は何でも良いです (よく event, e, evt などの名前が使われます)
イベントオブジェクト (2)
イベントオブジェクトのプロパティを通じて、様々な情報を取得できます
| プロパティ |
説明 |
target |
イベントが発生した要素 |
type |
イベントの種類("click" など) |
key |
押されたキー(keydown イベントの場合) |
イベントオブジェクト (3)
event.target を使って、クリックされた要素を取得できます。
const container = document.querySelector("#container");
function handleContainerClick(event) {
// クリックされた要素のテキストを表示
console.log("クリックされた要素:", event.target.textContent);
// クリックされた要素の背景色を変更
event.target.style.backgroundColor = "yellow";
}
container.addEventListener("click", handleContainerClick);
既定の動作の抑制
一部の要素は、ユーザーの入力に対して既定の動作(デフォルトの動作)を持っています (例: a要素がクリックされた場合は、対象のURLへ遷移する)
イベントオブジェクトの .preventDefault() というメソッドを使うことで、この動作をキャンセルすることができます。
link.addEventListener('click', (e) => {
if (!confirm('外部サイトへ移動します。よろしいですか?')) {
e.preventDefault(); // ユーザーがキャンセルしたら遷移を止める
}
});
イベントのバブリング (1)
HTML 要素は入れ子になっていることが多いです。
<div id="outer">
<div id="inner">
<button>クリック</button>
</div>
</div>
ボタンをクリックすると、イベントは以下の順で バブリング(泡のように上がっていく) します。
button → 2. #inner → 3. #outer → 4. body → 5. document
イベントのバブリング (2)
イベントのバブリングを使うことで、子要素のイベントをまとめて検知できる処理を親要素に書くことができます。
<ul id="list">
<li>項目1</li>
<li>項目2</li>
<li>項目3</li>
</ul>
// 親要素にだけリスナーを登録
const list = document.querySelector("#list");
list.addEventListener("click", handleListClick);
function handleListClick(event) {
// クリックされた li 要素に対して処理を実行
if (event.target.tagName === "LI") { /* 何か処理を書く */ }
}
イベントのバブリングを止める (1)
一方、「親要素でイベントを監視しているが、子要素のイベントでそれを実行したくない」場合もあります。
event.stopPropagation() を使うと、イベントのバブリングを止めることができます。
<div class="modal-overlay">
<div class="modal-content">
<p>モーダルの内容</p>
</div>
</div>
(デモ)
イベントのバブリングを止める (2)
const overlay = document.querySelector('.modal-overlay');
overlay.addEventListener("click", handleOverlayClick);
function handleOverlayClick() {
// モーダルを閉じる
overlay.style.display = "none";
}
const content = document.querySelector('.modal-content');
content.addEventListener("click", handleContentClick);
function handleContentClick(event) {
// handleOverlayClick() が呼び出されないようにする
event.stopPropagation();
}
補足: UIフレームワークについて
DOM の操作はとても強力な機能ですが、大規模なWebアプリケーションになるほど複雑性が増し、保守性が悪くなってしまいます。
そこで、最近のWebアプリケーションでは React や Vue.js などの フレームワーク を利用しているケースが多いです。フレームワークを使っていれば直接 DOM の関数を呼び出すことはあまりありませんが、これらのアプリケーションでも内部的に DOM の操作を行なっているため、DOM について理解していることが重要になります。
(参考) DOM の関数の場合と React の場合の例
let count = 0;
const countElement = document.getElementById('count');
const button = document.getElementById('btn');
function handleClick() {
count = count + 1;
countElement.textContent = count;
}
button.addEventListener('click', handleClick);
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<span>{count}</span>
<button onClick={handleClick}>増加</button>
</div>
);
まとめ
- DOM は、HTML を JavaScript から操作するための仕組み
document.querySelector() で要素を 1 つ取得
document.querySelectorAll() で要素を複数取得
addEventListener() でイベントリスナーを登録
- イベントオブジェクトから、イベントの詳細情報を取得できる
preventDefault() で既定の動作をキャンセル
- イベントはバブリングによって親要素にも伝わる
stopPropagation() でバブリングを止められる
さらに調べるには