jQuery / Selector / :has() - 特定の要素を含む要素を選択するセレクター

jQueryの:has(selector)は、指定条件に一致する子孫を1つ以上含む要素を選びます。$("#menu > li:has(a)")が返すのはリンクを持つliで、内側のaではありません。子孫には直接の子とさらに深い要素が含まれるため、直下だけを調べたい場合は条件を分けます。

含む要素と含まれる要素の違い

:has()の仕様は、該当する子孫を持つ候補を残すことを説明しています。span内のリンクもliの子孫なのでli:has(a)に一致します。一方、liのクラス名がhas-linkというだけでは、実際のaを含む条件には一致しません。

式 調べる条件 返す対象
li:has(a) 内側のどこかにaがある 条件に合うli
li:has(> a) aが直接の子である 条件に合うli
liを選び.has("a") 選択済みliにaの子孫がある 元のli集合の一部
liを選び.find("a") li内でaを探す 内側のa

>は直接の親子関係を示します。外側の#menu > liは候補の範囲、:has()内の> aは各候補の内部で調べる関係を決めます。この役割を分けて読むと、入れ子でも意図した条件を作れます。

リンクのある項目を強調する完全な例

全コードをUTF-8のhas-demo.htmlに保存してブラウザーで開きます。インストールは不要ですが、コンテンツ配信ネットワーク(CDN)から公式jQuery配布ファイルを読むため接続が必要です。例は4.0.0を使い、3.7.1と4.0.0で同じ動作を実行して確認しました。

plainにはリンクがなく、directは直下にa、nestedはspan内にaがあります。selfはhas-linkというクラス名だけです。ボタンで選択条件と文書変更を比較します。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Items that contain links</title>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 24px; }
    #menu { list-style: none; padding: 0; max-width: 600px; }
    #menu > li { border: 2px solid #ddd; margin: 8px 0; padding: 12px; }
    #menu > li.marked { border-color: #c45f00; background: #fff5e7; }
    button { margin: 4px; padding: 8px; }
  </style>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
</head>
<body>
  <h1>Items that contain links</h1>
  <ul id="menu">
    <li id="plain">No link</li>
    <li id="direct"><a id="link-1" href="#example">Direct link</a></li>
    <li id="nested"><span><a id="link-2" href="#example">Nested link</a></span></li>
    <li id="self" class="has-link">Class name only</li>
  </ul>
  <button type="button" id="descendants">Any descendant link</button>
  <button type="button" id="children">Direct child link</button>
  <button type="button" id="add">Add a link to plain</button>
  <button type="button" id="reset">Reset</button>
  <p id="result" aria-live="polite"></p>
  <script>
    $(function () {
      function show($chosen) {
        $("#menu > li").removeClass("marked");
        $chosen.addClass("marked");
        const ids = $chosen.map((_, item) => item.id).get().join(", ");
        $("#result").text(`${ids || "none"} | ${$chosen.length}`);
      }
      function refresh() {
        show($("#menu > li:has(a)"));
      }
      $("#descendants").on("click", refresh);
      $("#children").on("click", () => show($("#menu > li:has(> a)")));
      $("#add").on("click", function () {
        if (!$("#added-link").length) {
          $("#plain").append('<a id="added-link" href="#example"> Added link</a>');
        }
        refresh();
      });
      $("#reset").on("click", function () {
        $("#added-link").remove();
        refresh();
      });
      refresh();
    });
  </script>
</body>
</html>

最初はdirectとnestedの外枠が強調され、次を表示します。

direct, nested | 2

直接の子・追加・初期化を確認する

  1. Any descendant linkは深さを問わずリンクを調べ、direct, nested | 2です。
  2. Direct child linkは直下のaだけを調べ、direct | 1です。nestedのリンクはspanの下なので対象外です。
  3. Add a link to plainでリンクを追加するとplain, direct, nested | 3になります。再度押しても追加リンクのIDを確認して重複生成しません。
  4. Resetは追加したリンクだけを削除して標準の子孫条件を適用します。元のNo linkテキストや項目は残り、最初の2件に戻ります。

showは以前の強調を外して新しい結果にだけクラスを付けます。refreshは現在の文書を調べ直します。追加後に古い選択を使い続けることや、条件変更後も強調が残ることを防ぎます。

.has()と.find()を比較する

既にliを選んでいる場合は.has()メソッドで必要な候補を残せます。例を開いた直後かReset後に、開発者ツールのConsoleで次の部分コードを実行します。

const ids = $set => $set.map((_, item) => item.id).get().join(", ");
console.log(ids($("#menu > li:has(a)")));
console.log(ids($("#menu > li").has("a")));
console.log(ids($("#menu > li").find("a")));
direct, nested
direct, nested
link-1, link-2

最初の2つは外側のliのIDで、.find()は内側のaのIDです。.has()は親を探して上へ移動する処理ではなく、選択済みの集合を子孫条件で絞る処理です。候補自身に条件を適用する.filter()とも異なります。

.has()には選択文字列のほか、文書オブジェクトモデル(DOM)の特定要素も渡せます。その要素を子孫として含む候補を残します。候補自身だけが一致する場合や、一致する子孫がない場合は空の結果になります。

選択範囲・再選択・CSSとの関係

$(":has(a)")のように文書全体を候補にすると、リンクを含む複数の祖先まで選ぶ可能性があります。目的に応じてコンテナーとタグを限定します。選択結果は自動更新されないため、子孫の追加・削除後は選択し直して現在の構造を反映します。

Selectors Level 4にはCSSの:has()も定義されているため、常にjQuery専用と説明するのは正確ではありません。この記事はjQuery APIで要素集合を得る方法を扱います。CSS規則で見た目だけを変える方法と比較するときは、対象ブラウザーと必要な選択条件を確認します。

反応しない場合はNetworkでjQueryの読み込み、Consoleでエラーを確認します。括弧・引用符、実際のタグと入れ子の深さも調べます。表示上の「リンク」という文字やクラス名ではなく、a要素の構造が条件です。