closest とは何か
closest は、ある要素から「自分自身を含めて、上方向(祖先方向)にセレクタに一致する最初の要素」を返すメソッドです。見つかればその要素、見つからなければ null。ここが重要です:HTML の入れ子構造が多少変わっても、「役割(セレクタ)」で祖先を特定できるため、DOM 構造変更に強いコードを書けます。
基本の使い方(自分から上に遡って探す)
シンプルな例
<article class="card">
<header class="card-header">
<button class="buy">購入</button>
</header>
</article>
<script>
const btn = document.querySelector(".buy");
const card = btn.closest(".card");
console.log(card.tagName); // "ARTICLE"
</script>
HTMLこの例では、button から見て上方向に「.card」を探し、最初に見つかった article を返します。自分自身がセレクタに一致する場合は、自分が返ります。
自身が一致する場合
<div class="panel">
<div class="panel inner">
<button class="panel">OK</button>
</div>
</div>
<script>
const btn = document.querySelector("button.panel");
console.log(btn.closest(".panel") === btn); // true(自分が一致)
</script>
HTMLここが重要です:closest は「自分を含めて」判定します。まず自分、次に親、さらにその親…と上へ辿ります。
parentElement と querySelector との違い(どれを使うかの軸)
parentElement は「直近の親だけ」
const el = document.querySelector(".buy");
const parent = el.parentElement; // 直近の親
JavaScript直近の親が欲しいだけならこれで十分ですが、条件に合う祖先を探すなら closest が適切です。
querySelector は「下方向の検索」
const card = document.querySelector(".card");
// 下方向(子孫)にあるボタンを探す
const buy = card.querySelector(".buy");
JavaScriptquerySelector は子孫方向の探索。closest は祖先方向の探索。ここが重要です:「上へ辿るなら closest、下へ探すなら querySelector」という役割分担で迷いが消えます。
イベント委譲での実践(最小登録で確実に特定)
クリックした要素が“あるコンポーネント内”かを判定
<ul class="menu">
<li class="item"><a class="link" href="#">A</a></li>
<li class="item"><a class="link" href="#">B</a></li>
</ul>
<script defer>
const menu = document.querySelector(".menu");
menu.addEventListener("click", (e) => {
const item = (e.target as Element).closest(".item"); // 祖先の .item を特定
if (!item || !menu.contains(item)) return; // メニュー外のクリックは無視
item.classList.toggle("is-active");
});
</script>
HTMLここが重要です:イベントは親(.menu)に1つだけ登録し、発生源から closest で目的の祖先を特定する。子の追加・削除に強く、登録数も増えないためスケールします。
セレクタ設計の勘所(安定したフックを選ぶ)
data-* を使うと構造変更に強い
<article class="card" data-role="product">
<h2 data-role="title">Title</h2>
<button class="buy">購入</button>
</article>
<script>
const btn = document.querySelector(".buy");
const product = btn.closest('[data-role="product"]');
// 入れ子が変わっても、役割で祖先を特定
</script>
HTMLここが重要です:見た目用クラス(レイアウト変更で変わりやすい)より、id や data-* の「役割フック」をセレクタに使うと、closest の信頼性が上がります。
よくある落とし穴と回避策(null・境界・shadow DOM)
見つからないときは null
const el = document.querySelector(".buy");
const modal = el.closest(".modal");
if (!modal) {
// モーダル外ならスキップなど
}
JavaScriptここが重要です:必ず null ガードを入れる。祖先に期待のコンテナが存在しないケースは普通に起こります。
範囲外のクリックを除外
親の中だけで動かしたい場合は、contains を併用すると安全です。
const root = document.querySelector(".root");
root.addEventListener("click", (e) => {
const target = e.target as Element;
const inside = target.closest(".root");
if (!inside || inside !== root) return; // root 外は無視
});
JavaScriptshadow DOM 内ではスコープが分かれる
Web コンポーネントの shadowRoot 内でも closest は使えますが、影の外側は辿れません(閉じたスコープ)。コンポーネント間を跨ぐ設計は「外からイベントを受け取る仕組み」を用意します。
まとめ
closest は「自分から上へ辿って、セレクタに一致する最初の祖先(自分を含む)」を返す、祖先探索の決定版です。直近の親なら parentElement、子孫探索なら querySelector、と役割を分ける。イベント委譲との相性が抜群で、クリック発生源から目的のコンテナを即特定でき、登録数を増やさずに堅牢な UI を作れます。data-* など安定したフックを使い、常に null ガードを入れる——これだけで closest は初心者の強い味方になります。
