JavaScript | DOM 操作:イベント基礎 – preventDefault

JavaScript
スポンサーリンク

preventDefault とは何か

preventDefault は「イベントのデフォルト動作(ブラウザが標準で行う処理)」をキャンセルするためのメソッドです。例えばリンクの遷移、フォームの送信、スクロール、右クリックメニューなどを“止めて”自分の処理に置き換えたいときに使います。ここが重要です:すべてのイベントが止められるわけではありません。e.cancelable が true のイベントだけ有効で、止めた結果は e.defaultPrevented で確認できます。


基本の使い方とキャンセル可能性の確認

最小の例(リンク遷移を止めて SPA へ切り替え)

<a id="go" href="/next">次へ</a>
<script>
  const go = document.getElementById("go");
  go.addEventListener("click", (e) => {
    if (!e.cancelable) return;          // キャンセル可能性の保険
    e.preventDefault();                  // 標準のページ遷移を止める
    go.textContent = "移動中…";
    history.pushState({}, "", "/next");  // アプリ側の遷移
  });
</script>
HTML

ここが重要です:止める理由が“明確”なときだけ使う。止めた後に何をするか(代替処理)を必ず用意します。

止めたかどうかの確認(defaultPrevented)

document.addEventListener("click", (e) => {
  // すでに別のハンドラが preventDefault したか
  if (e.defaultPrevented) return;
  // ここで通常処理
});
JavaScript

ここが重要です:複数のハンドラが同じイベントを見るとき、既定動作を止めたかどうかの“合図”として使えます。


代表的な場面と正しい使い方

フォーム送信(submit)を非同期へ切り替える

<form id="f">
  <input name="email" type="email" required>
  <button type="submit">送信</button>
</form>
<script>
  f.addEventListener("submit", async (e) => {
    // 検証に通らないなら既定送信を止めて案内
    if (!f.checkValidity()) { e.preventDefault(); f.reportValidity(); return; }

    // SPA では既定送信を止めて fetch に置換
    e.preventDefault();
    const btn = f.querySelector("button");
    btn.disabled = true; btn.textContent = "送信中…";
    try {
      const data = new FormData(f);
      const res = await fetch("/api", { method: "POST", body: data });
      if (!res.ok) throw new Error("失敗");
    } finally {
      btn.disabled = false; btn.textContent = "送信";
    }
  });
</script>
HTML

ここが重要です:ブラウザの制約検証(required/type/pattern)を活かした上で、非同期送信に切り替えるときだけ preventDefault。状態(文言・disabled)を一つのロジックで同期させると安定します。

キーの既定動作(Enter・Backspace)を文脈で止める

<input id="q" placeholder="検索">
<script>
  q.addEventListener("keydown", (e) => {
    if (e.key === "Enter") {
      e.preventDefault();                 // 既定の submit を止める
      console.log("検索:", q.value.trim());
    }
  });

// 入力要素でない場面の Backspace の「戻る」を抑止
  document.addEventListener("keydown", (e) => {
    const el = document.activeElement.tagName;
    const inInput = el === "INPUT" || el === "TEXTAREA";
    if (!inInput && e.key === "Backspace") e.preventDefault();
  });
</script>
HTML

ここが重要です:ユーザーの期待(Enterで送信・Backspaceで編集)を裏切らないよう、止める範囲と条件を厳密に絞る。

スクロールやホイール(passive 設定との関係)

<div id="panel" style="height:120px; overflow:auto"></div>
<script>
// スクロールを止めたいときは passive: false が必須
panel.addEventListener("wheel", (e) => {
  const shouldLock = /* 条件 */;
  if (shouldLock) e.preventDefault();    // 条件付きでスクロール抑止
}, { passive: false });
</script>
HTML

ここが重要です:{ passive: true } で登録したハンドラでは preventDefault できません。スクロール最適化(passive: true)と抑止(passive: false)を場面で使い分けます。

右クリックのコンテキストメニューを置き換える

<div id="area">右クリックでメニュー</div>
<script>
  area.addEventListener("contextmenu", (e) => {
    e.preventDefault(); // 標準メニューを止める
    // 独自メニュー表示(e.clientX, e.clientY で位置指定)
  });
</script>
HTML

ここが重要です:常時抑止は不便になりがち。独自メニューが必要な特定領域だけに限定します。

ドラッグ&ドロップ(dragover/drop の既定動作)

<div id="drop">ここにファイルをドロップ</div>
<script>
  ["dragenter","dragover"].forEach(type => {
    drop.addEventListener(type, (e) => { e.preventDefault(); }); // 受け入れ宣言
  });
  drop.addEventListener("drop", (e) => {
    e.preventDefault();
    const files = e.dataTransfer.files;
    console.log("受信:", files.length, "件");
  });
</script>
HTML

ここが重要です:dragover を止めないとブラウザは“開く”などの既定処理をしてしまい、ドロップを受け取れません。


preventDefault と stopPropagation の違いを徹底理解

役割の違いをコードで確認

<div id="outer" style="padding:10px;border:1px solid #ccc">
  <button id="inner">押す</button>
</div>
<script>
  outer.addEventListener("click", () => console.log("外側(バブリングで到達)"));

  inner.addEventListener("click", (e) => {
    // e.preventDefault();    // 既定動作(button の submit など)を止める
    // e.stopPropagation();   // 外側へ伝播しなくなる
    console.log("内側のみ");
  });
</script>
HTML

ここが重要です:“送信・遷移・スクロールなどの既定動作”を止めたいなら preventDefault。“親のハンドラを発火させたくない”なら stopPropagation。混同すると意図通りに動きません。


設計指針(いつ止め、いつ通すか)

明確な理由がある場面のみ止める

  • SPA のリンク制御や非同期フォーム送信など、代替処理が準備できているとき。
  • ドラッグ&ドロップの受け入れ、独自コンテキストメニューなど、標準挙動と目的が衝突するとき。

ここが重要です:まず「通す」設計を検討し、アクセシブルな要素(button, a, form)を正しく使う。どうしても必要な場所だけを最小範囲で止めるのが上級者の作法です。

スコープを絞る

document 全体で一律抑止せず、対象要素にだけハンドラを付けます。ここが重要です:副作用を減らし、予期しない場面でユーザー体験を壊さない。

IME・検証・状態を考慮する

日本語入力の確定前は処理を控える(composition 系イベント)、フォームは checkValidity/reportValidity を活用し、送信中の二重操作を防ぐため UI 状態を単一ロジックで同期します。


よくある落とし穴と回避策

無闇な preventDefault の乱用

何でも止めると、ユーザーの期待(リンクが開く、フォームが送信される)を裏切り、アクセシビリティを損ねます。必要性を説明できる場面だけ使いましょう。

passive: true のままスクロールを止めようとする

スクロール抑止は { passive: false } が前提。設定の整合性を常に確認します。

stopPropagation との取り違え

既定動作を止めたいのに stopPropagation を使う、といった誤用に注意。役割を頭に刻み込んで使い分けます。

キャンセル不可イベントへの適用

e.cancelable が false のイベントには効果がありません。止められないものは CSS(overflow, user-select)や構造変更で対応します。


実践例(まとめ)

SPA リンク制御(委譲で最小抑止)

<nav id="nav">
  <a href="/home">Home</a>
  <a href="/about">About</a>
</nav>
<script>
  nav.addEventListener("click", (e) => {
    const a = e.target.closest("a");
    if (!a) return;
    e.preventDefault();
    history.pushState({}, "", a.getAttribute("href"));
    // ここでビュー更新
  });
</script>
HTML

非同期フォーム(検証+状態同期)

<form id="f">
  <input name="email" type="email" required>
  <button type="submit">送信</button>
</form>
<script>
  f.addEventListener("submit", async (e) => {
    if (!f.checkValidity()) { e.preventDefault(); f.reportValidity(); return; }
    e.preventDefault();
    const btn = f.querySelector("button");
    btn.disabled = true; btn.textContent = "送信中…";
    try { await new Promise(r => setTimeout(r, 800)); }
    finally { btn.disabled = false; btn.textContent = "送信"; }
  });
</script>
HTML

ドラッグ&ドロップ受け入れ

<div id="drop">ここへドロップ</div>
<script>
  ["dragenter","dragover"].forEach(t => {
    drop.addEventListener(t, (e) => e.preventDefault());
  });
  drop.addEventListener("drop", (e) => {
    e.preventDefault();
    console.log("files:", e.dataTransfer.files.length);
  });
</script>
HTML

まとめ

preventDefault は「ブラウザの既定動作」を止めて、自分の処理に置き換えるための基本メソッドです。e.cancelable/e.defaultPrevented を理解し、passive と整合を取り、stopPropagation とは役割を明確に分ける。フォームは検証を活かした上で非同期へ切り替え、リンクやドラッグ&ドロップは必要最小限の抑止で扱う。“通すのが基本、止めるのは例外”の設計を徹底すれば、初心者でも体験を損なわずに意図通りのイベント制御が書けます。

タイトルとURLをコピーしました