📝 注意: 日本語が得意ではないため、翻訳ツールを使用して作成しました。読みにくい点があればご容赦ください🙇
目次
-
イベントフロー — DOMイベントの流れ
- 1.1. Capturing・Target・Bubblingの3フェーズ
- 1.2. フロー図
-
イベントを制御する3つのメソッド
- 2.1.
stopPropagation()— 意図しない伝播を防ぐ - 2.2.
stopImmediatePropagation()— 残りのリスナーをすべて止める - 2.3.
preventDefault()— デフォルト動作を防ぐ
- 2.1.
- まとめ
はじめに
この記事の目的
UIを実装しているとき、こんな困った経験はありませんか?
- ボタンをクリックしたらイベントが複数回発火したり、意図しない場所で発火したりする
- モーダルの内側をクリックしただけで閉じてしまう(外側のクリックで閉じてほしいのに)
- フォームを送信するとAJAXではなくページがリロードされる
- input欄でEnterを押すと予期しない動作が起きる
これらの問題はすべて、Event Flow(イベントフロー)とその制御方法に関係しています。
この記事で学べること:
- DOMのイベントフローを正しく理解する(Capturing → Target → Bubbling)
-
3つの重要メソッドの違いと使い分けを習得する:
stopPropagation()・stopImmediatePropagation()・preventDefault() - UIのデバッグを効率化する — イベントが期待通りに動かない場面で素早く原因を特定できるようになる
この記事で得られる知識
| 知識 | 内容 |
|---|---|
| DOMイベントフロー | Capturing・Target・Bubblingの3フェーズを理解する |
stopPropagation() |
イベントが親要素へ伝播するのを防ぐ |
stopImmediatePropagation() |
伝播を防ぎつつ、同じ要素の他のリスナーも止める |
preventDefault() |
ブラウザのデフォルト動作を防ぐ |
| Reactでの活用 | 具体的なコード例をReactで紹介 |
1. イベントフロー — DOMイベントの流れ
あるDOM要素でイベントが発生したとき(例:ボタンのクリック)、そのイベントはその要素だけで完結しません。イベントは3つのフェーズを経て伝播します。
-
キャプチャリングフェーズ(Capturing): イベントはDOMの最上位(
window/document)からターゲット要素へ向かって下方向に伝播します。 - ターゲットフェーズ(Target): イベントが実際に操作された要素に到達します。
- バブリングフェーズ(Bubbling): イベントはターゲット要素からDOMの最上位へ向かって上方向に伝播します。
フロー図
CAPTURING — 下方向への伝播
─────────────────────────────
window
│
▼
document
│
▼
html
│
▼
body
│
▼
div
│
▼
[button] ← TARGET(ターゲット要素)
BUBBLING — 上方向への伝播
─────────────────────────────
window
▲
│
document
▲
│
html
▲
│
body
▲
│
div
▲
│
[button] ← ここから開始
2. イベントを制御する3つのメソッド
2.1. stopPropagation() — 意図しない伝播を防ぐ
問題: モーダルの内側をクリックしたのに、イベントが外側のオーバーレイにバブリングしてモーダルが閉じてしまう。
解決策: stopPropagation() を呼び出して、イベントが親要素へ伝播するのを止める。
function Modal() {
const closeModal = () => {
console.log('モーダルを閉じる');
// モーダルを閉じる処理
};
const handleContentClick = (e) => {
e.stopPropagation(); // ← クリックイベントが親に伝わらない
console.log('モーダルの中身をクリックしました');
};
return (
<div
className="modal-overlay"
onClick={closeModal} // オーバーレイをクリック → モーダルを閉じる
>
<div
className="modal-content"
onClick={handleContentClick} // 内側をクリック → 閉じない
>
<h2>モーダルタイトル</h2>
<p>これはモーダルの内容です</p>
<button>閉じる</button>
</div>
</div>
);
}
結果:
- オーバーレイ(背景)をクリック → モーダルが閉じる ✅
- モーダルの内側をクリック → モーダルは閉じない ✅(
stopPropagation()が伝播を止めるため)
2.2. stopImmediatePropagation() — 残りのリスナーをすべて止める
問題: 1つの要素に同じイベントのリスナーが複数登録されている。最初のリスナーだけを実行して、残りはすべて止めたい。
stopPropagation() との違い:
| メソッド | 親への伝播を止める? | 同じ要素の他のリスナーを止める? |
|---|---|---|
stopPropagation() |
✅ 止める | ❌ 止めない(他のリスナーは実行される) |
stopImmediatePropagation() |
✅ 止める | ✅ 止める(即座にすべてブロック) |
function ButtonWithMultipleListeners() {
useEffect(() => {
const button = document.getElementById('myButton');
// リスナー1
button?.addEventListener('click', (e) => {
e.stopImmediatePropagation(); // ← 以降のリスナーをすべてブロック
console.log('リスナー1 - 優先処理(例:認証チェック)');
});
// リスナー2 — 実行されない
button?.addEventListener('click', () => {
console.log('リスナー2 - このメッセージは表示されない');
});
// リスナー3 — 実行されない
button?.addEventListener('click', () => {
console.log('リスナー3 - これも実行されない');
});
}, []);
return <button id="myButton">クリック</button>;
}
使いどころ: 認証チェックやセキュリティログなど、優先度の高いリスナーを確実に先に実行し、条件を満たさない場合に後続の処理をすべてブロックしたいとき。
2.3. preventDefault() — デフォルト動作を防ぐ
問題: フォームをsubmitするとページがリロードされる。リンクをクリックすると別ページへ飛ぶ。Enterキーで意図せずフォームが送信される。
解決策: preventDefault() を呼び出してブラウザのデフォルト動作を防ぐ。
重要なポイント:
preventDefault()はイベントフローには影響しません — CapturingフェーズとBubblingフェーズは通常通り進みます。
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // ← ページのリロードを防ぐ
console.log('フォーム送信:', { email, password });
// APIを呼び出してログイン処理
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード"
/>
<button type="submit">ログイン</button>
</form>
);
}
preventDefault() のよくある使用例:
| 状況 | 防ぐデフォルト動作 | 目的 |
|---|---|---|
<form> のsubmit |
ページのリロード | AJAXでデータ送信(リロードなし) |
<a> タグのクリック |
別ページへの遷移 | React Routerによるクライアントサイドルーティング |
| 右クリック | ブラウザのコンテキストメニュー表示 | カスタムメニューを表示する |
| inputでのキー入力 | 文字の入力 | 数字のみ入力を許可する |
3. まとめ
3つのメソッドの早見表
| メソッド | 親への伝播を止める? | 同じ要素の他のリスナーを止める? | デフォルト動作を防ぐ? |
|---|---|---|---|
stopPropagation() |
✅ | ❌ | ❌ |
stopImmediatePropagation() |
✅ | ✅ | ❌ |
preventDefault() |
❌ | ❌ | ✅ |
覚え方のコツ
| メソッド | 覚え方 |
|---|---|
stopPropagation() |
Propagation(伝播)→ イベントが親要素へ伝わるのを止める |
stopImmediatePropagation() |
Immediate(即座に)→ すべてを即座に止める(同じ要素の他のリスナーも含む) |
preventDefault() |
Default(デフォルト)→ ブラウザの組み込み動作を止める |
どのメソッドを使うべき? デバッグ早見表
| 発生している現象 | 考えられる原因 | 解決策 |
|---|---|---|
| 1つのボタンをクリックしたのに複数の箇所が反応する | イベントが親要素へバブリングしている | stopPropagation() |
| モーダルの内側をクリックしたらモーダルが閉じた | クリックイベントがオーバーレイへ伝播している | stopPropagation() |
| リスナーが複数あるが1つだけ実行したい | 他のリスナーも実行されてしまっている | stopImmediatePropagation() |
| フォームをsubmitしたらページがリロードされた |
<form> のデフォルト動作 |
preventDefault() |
| リンクをクリックしたら意図せずページ遷移した |
<a> タグのデフォルト動作 |
preventDefault() |
おわりに
Event Flow(Capturing → Target → Bubbling)を正しく理解することは、JavaScriptでイベントを思い通りに制御するための基礎です。stopPropagation()・stopImmediatePropagation()・preventDefault() の3つを使いこなすことで、以下が実現できます。
- 意図しないイベントの伝播を防ぐ
- 複雑な状況での処理の優先順位を制御する
- ブラウザのデフォルト動作を目的に合わせてカスタマイズする
ぜひサンプルコードを手を動かして試してみてください!
Happy coding! 🚀
参考
e.preventDefault() で防げるブラウザのデフォルトイベントの一覧については、以下の記事をご参照ください。
