イベントオブジェクトとは何か
イベントオブジェクト(一般に e と書く)は、「何が、どこで、どのように起きたか」の詳細を持つ“出来事の記録”です。addEventListener のコールバックに渡され、操作対象の要素、イベントの種類、発生時刻、修飾キー、座標、既定動作の制御などにアクセスできます。ここが重要です:イベント処理は「要素」ではなく「文脈(e)」を読む。e を使いこなすほど、正確で壊れにくいインタラクションが書けます。
基本プロパティ(type、target、currentTarget、timeStamp)
どのイベントか、どこで起きたか
<ul id="list">
<li>りんご</li><li>バナナ</li>
</ul>
<script>
const list = document.getElementById("list");
list.addEventListener("click", (e) => {
console.log(e.type); // "click"(イベントの種類)
console.log(e.target); // 実際にクリックされた“最も内側”の要素
console.log(e.currentTarget); // リスナーが付いている要素(ここでは UL)
console.log(e.timeStamp); // 発生時刻(ms)
});
</script>
HTMLここが重要です:イベント委譲では target で“誰が触られたか”を判定、currentTarget は“どこにリスナーがあるか”。両者の違いを常に意識します。
既定動作と伝播の制御(preventDefault/stopPropagation)
<a id="link" href="/next">次へ</a>
<script>
link.addEventListener("click", (e) => {
e.preventDefault(); // 既定のページ遷移を止める
e.stopPropagation(); // 親への伝播を止める(必要な場合のみ)
history.pushState({}, "", "/next"); // SPA 的な遷移
});
</script>
HTMLここが重要です:preventDefault は“ブラウザの標準動作を置き換える”ときだけ使う。stopPropagation は“外側の処理を発火させたくない”ときに限定して使い、乱用を避けます。
入力・編集系の文脈(input、change、composition)
入力途中/確定後/IME合成の違い
<input id="q" placeholder="検索">
<script>
const q = document.getElementById("q");
q.addEventListener("input", (e) => console.log("途中:", e.target.value));
q.addEventListener("change", (e) => console.log("確定:", e.target.value));
q.addEventListener("compositionstart", () => console.log("IME開始"));
q.addEventListener("compositionend", () => console.log("IME確定"));
</script>
HTMLここが重要です:日本語入力では composition 中の値は“未確定”。厳密なバリデーションや検索実行は compositionend 以降に行うと誤判定が減ります。
フォーム送信の文脈(submit の e)
<form id="f">
<input name="email" type="email" required>
<button>送信</button>
</form>
<script>
f.addEventListener("submit", (e) => {
e.preventDefault(); // 既定送信を停止
const form = e.currentTarget; // フォームそのもの
console.log(form.email.value); // 名前で要素参照
});
</script>
HTMLここが重要です:submit では e.currentTarget が“フォーム本体”。ここから安全に値へアクセスします。
マウス・キーボードの詳細(座標・キー・修飾)
マウスイベントの座標とボタン
<div id="box" style="height:100px;border:1px solid #ccc"></div>
<script>
box.addEventListener("click", (e) => {
console.log(e.clientX, e.clientY); // ビューポート座標
console.log(e.pageX, e.pageY); // ドキュメント座標
console.log(e.button); // 0:左, 1:中, 2:右
});
</script>
HTMLここが重要です:ドラッグや描画系では座標系(client/page)を使い分けます。スクロール影響を受けないなら client、受けるなら page が扱いやすいです。
キー入力と修飾キー
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
e.preventDefault(); // ブラウザ保存を抑止
console.log("アプリの保存");
}
});
JavaScriptここが重要です:ショートカットは e.key と修飾キー(ctrlKey、metaKey、shiftKey、altKey)の組み合わせで判定。既定動作が重なる場合は preventDefault を忘れない。
フォーカス・ポインタ・スクロール(UI 状態の合わせ方)
フォーカスの文脈(focus/blur)
<input id="name">
<script>
name.addEventListener("focus", (e) => console.log("フォーカスIN:", e.target));
name.addEventListener("blur", (e) => console.log("フォーカスOUT:", e.target));
</script>
HTMLここが重要です:フォーカスイベントはバブリングしないことに注意(focusin/focusoutはバブリングします)。親でまとめて扱うなら focusin/focusout が便利です。
低コストスクロール(passive の前提)
window.addEventListener("scroll", (e) => {
// 軽い表示更新のみ。preventDefault はしない。
}, { passive: true });
JavaScriptここが重要です:スクロールは高頻度。passive: true で“スクロールを止めない”宣言をすると、ブラウザが最適化でき体感が軽くなります。
伝播の仕組み(キャプチャ/ターゲット/バブリング)
どの段階で処理されるか
<div id="outer"><button id="inner">押す</button></div>
<script>
outer.addEventListener("click", () => console.log("バブリング:外"));
outer.addEventListener("click", () => console.log("キャプチャ:外"), { capture: true });
inner.addEventListener("click", () => console.log("ターゲット:内"));
</script>
HTMLここが重要です:イベントは「キャプチャ → ターゲット → バブリング」の順に伝わる。特殊要件で“先に拾いたい”なら capture を使い、通常はバブリングで十分です。
カスタムイベント(アプリ内の合図を自分で作る)
自作の出来事を発火する
<div id="bus"></div>
<script>
bus.addEventListener("loaded", (e) => console.log("完了:", e.detail));
const ev = new CustomEvent("loaded", { detail: { count: 42 } });
bus.dispatchEvent(ev);
</script>
HTMLここが重要です:CustomEvent の detail に“必要な文脈”を詰めて合図を送る。DOM の仕組み(伝播・委譲)をそのまま使えるため、疎結合な設計に向きます。
実践例(委譲で削除、ドラッグ座標、IME対応検索)
親で受ける委譲と target 判定
<ul id="items"></ul>
<script>
items.addEventListener("click", (e) => {
const del = e.target.closest(".delete");
if (!del) return;
del.closest("li")?.remove();
});
// 追加しても動く
const li = document.createElement("li");
li.innerHTML = `りんご <button class="delete">削除</button>`;
items.appendChild(li);
</script>
HTMLここが重要です:target+closest で“どの塊が対象か”を判定。動的追加にも強く、リスナーの数を増やさずに済みます。
マウスドラッグの座標取得
<div id="pad" style="height:120px;border:1px solid #ccc"></div>
<script>
let drawing = false;
pad.addEventListener("mousedown", () => drawing = true);
pad.addEventListener("mouseup", () => drawing = false);
pad.addEventListener("mousemove", (e) => {
if (!drawing) return;
const x = e.clientX - pad.getBoundingClientRect().left;
const y = e.clientY - pad.getBoundingClientRect().top;
console.log(x, y); // パッド内座標
});
</script>
HTMLここが重要です:座標は“要素の境界からの差”で正規化して扱う。UI のブレが減り、実装が安定します。
IME対応の検索トリガ
<input id="q" placeholder="検索">
<script>
let composing = false;
q.addEventListener("compositionstart", () => composing = true);
q.addEventListener("compositionend", () => { composing = false; trigger(); });
q.addEventListener("input", () => { if (!composing) trigger(); });
function trigger() { console.log("検索:", q.value.trim()); }
</script>
HTMLここが重要です:composition 中は“未確定”なので実行を避け、compositionend で確定後に走らせる。日本語入力の現実に即した設計です。
まとめ
イベントオブジェクトは「出来事の文脈」を運ぶ中心的な情報源です。type/target/currentTarget/timeStamp を起点に状況を把握し、preventDefault/stopPropagation で動作と伝播を制御。入力は input/change/composition を使い分け、マウス・キーボードは座標・キー・修飾キーで精密に判定。伝播段階(capture/target/bubbling)を理解し、委譲で規模に強い設計にする。必要なら CustomEvent で合図を作る。e を正しく読む癖をつければ、初心者でも滑らかで壊れにくいイベント処理が書けます。
