JavaScript | DOM 操作:イベント基礎 – target と currentTarget

JavaScript
スポンサーリンク

target と currentTarget とは何か

イベントオブジェクトの target は「実際に操作された最も内側の要素」、currentTarget は「そのイベントリスナーが登録されている要素」を指します。ここが重要です:委譲(親にリスナーを付けて子の操作に反応する)では、判定に target、操作の基準には currentTarget を使い分けます。同じクリックでも値が違うことが本質で、混同するとバグになります。


直感で理解する例(子をクリック、親で受ける)

親にリスナー、子をクリックしたときの違い

<div id="parent" style="padding:8px;border:1px solid #ccc">

  <button id="child">子ボタン</button>
</div>
<script>
  parent.addEventListener("click", (e) => {
    console.log("target:", e.target.id);         // 例: "child"(実際に押されたのは子)
    console.log("currentTarget:", e.currentTarget.id); // "parent"(リスナーは親)
  });
</script>
HTML

ここが重要です:クリックの発生源(target)は子、処理の所有者(currentTarget)は親。委譲パターンでは、この差を使って“どの子が押されたか”を判断します。

子にリスナーを付けた場合

child.addEventListener("click", (e) => {
  console.log(e.target === child);        // 常に true(最内側が子ボタン)
  console.log(e.currentTarget === child); // 常に true(リスナーも子ボタン)
});
JavaScript

ここが重要です:リスナーが子にあれば両方同じになります。差が出るのは“親に付けて子の操作に反応する”ときです。


実用パターン(イベント委譲で規模に強くする)

親一箇所で子の削除ボタンに反応

<ul id="list"></ul>
<script>
  const list = document.getElementById("list");

  list.addEventListener("click", (e) => {
    // クリックされた要素から .delete ボタンまで遡る
    const delBtn = e.target.closest(".delete");
    if (!delBtn || !list.contains(delBtn)) return; // 別領域のクリックなら無視

    // 行(li)を削除。基準は現在の親(currentTarget)
    const row = delBtn.closest("li");
    row?.remove();
  });

  // 後から追加しても問題なく動作
  const li = document.createElement("li");
  li.innerHTML = `りんご <button class="delete">削除</button>`;
  list.appendChild(li);
</script>
HTML

ここが重要です:委譲は「親に1つだけリスナー」を付けるため、動的追加・大量要素でも軽く保てます。判定は target+closest、操作の範囲確認に currentTarget.contains(…) を使うと安全です。


伝播段階との関係(キャプチャ/ターゲット/バブリング)

どの段階でも target は“発生源”、currentTarget は“今処理中”

<div id="outer"><button id="inner">押す</button></div>
<script>
  outer.addEventListener("click", (e) => {
    console.log("バブリング:", e.target.id, e.currentTarget.id);
  });
  outer.addEventListener("click", (e) => {
    console.log("キャプチャ:", e.target.id, e.currentTarget.id);
  }, { capture: true });
  inner.addEventListener("click", (e) => {
    console.log("ターゲット:", e.target.id, e.currentTarget.id);
  });
</script>
HTML

ここが重要です:どの段階でも target は“最初にクリックされた要素”。currentTarget は“今このハンドラが付いている要素”。段階が違っても定義は変わりません。


よくある落とし穴と回避策(this、停止、判定のズレ)

this を使わず currentTarget を使う

アロー関数では this が外側に束縛されるため、要素参照に不向きです。ここが重要です:常に e.currentTarget を使う癖にすると、関数の種類や bind 有無に影響されず安定します。

stopPropagation の乱用を避ける

inner.addEventListener("click", (e) => {
  // e.stopPropagation(); // 本当に必要な場合だけ
});
JavaScript

ここが重要です:外側で委譲している設計では、安易な stopPropagation が“外側の正常処理”を壊します。必要性を検討してから使いましょう。代わりに条件分岐(closest 判定)で丁寧に絞り込む方が健全です。

target は入れ替わることがある(影響の理解)

CSS の ::before/::after、SVG、シャドウDOMなどでは、実際のクリック先のノードが思っている要素でないことがあります。ここが重要です:常に closest(‘望むセレクタ’) で“意図した塊”へ正規化し、currentTarget.contains(…) で範囲チェックするのが堅牢です。


実践例(タブ切り替え、メニュー開閉、アクセスログ)

タブを親一箇所で切り替え

<div id="tabs">
  <button data-tab="a">A</button>
  <button data-tab="b">B</button>
</div>
<div id="panel-a">A パネル</div>
<div id="panel-b" hidden>B パネル</div>
<script>
  tabs.addEventListener("click", (e) => {
    const btn = e.target.closest("#tabs [data-tab]");
    if (!btn) return;
    const tab = btn.dataset.tab;

    // currentTarget(tabs)を基準に、UI を同期
    document.querySelector("#panel-a").hidden = tab !== "a";
    document.querySelector("#panel-b").hidden = tab !== "b";
  });
</script>
HTML

ここが重要です:判定は target、操作は currentTarget 基準で範囲を限定。タブが増えてもリスナーは1つのままです。

メニューの外クリックで閉じる

<div id="menu" class="open">…</div>
<script>
  document.addEventListener("click", (e) => {
    const menu = document.getElementById("menu");
    if (!menu.classList.contains("open")) return;
    // currentTarget は document、target がメニュー外なら閉じる
    if (!menu.contains(e.target)) menu.classList.remove("open");
  });
</script>
HTML

ここが重要です:contains 判定で“外側クリック”を見分ける。target をそのまま信じず、意図した領域との関係で判断します。

クリックログを正確に記録

document.addEventListener("click", (e) => {
  const actor = e.target.closest("[data-track]");
  if (!actor) return;
  console.log({
    type: e.type,
    actor: actor.dataset.track,
    owner: e.currentTarget === document ? "doc" : "other",
    at: e.timeStamp
  });
});
JavaScript

ここが重要です:actor(誰が操作されたか)は target+closest、owner(誰が処理しているか)は currentTarget。文脈を分けて記録すると後で分析しやすいです。


まとめ

target は「実際に操作された最内側の要素」、currentTarget は「リスナーが登録された要素」。委譲では target で“どの子か”を判定し、操作は currentTarget 基準で範囲を限定する。this ではなく currentTarget を使って文脈依存を排除し、stopPropagation の乱用を避け、closest+contains で“意図した塊”に正規化する。これを徹底すれば、初心者でも規模に強く、読みやすく、壊れにくいイベント処理が書けます。

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