JavaScriptで最も近い子孫要素を取得・操作・判別する方法(closest()の逆メソッド)【querySelectorAll()】

JavaScript

先祖要素を指定するclosest()メソッドとは反対に、子孫要素を指定するメソッドは何でしょうか?「それはfind()メソッド」です、と書いてあるサイトはすぐさま読むのを止めましょう。

2,3歩譲歩すれば完全に間違ってはいませんが、find()を使って子孫要素を取得するのは「jQuery」です。実はjQueryでは直近の先祖要素を走査する際にもclosest()メソッドは使います。ただし、子孫要素に関してはfind()メソッドは利用できません。ちなみにJavaScriptにおけるfind()は配列周りのメソッドなのでここでは触れません。

querySelectorAll()を使って直近の子孫要素を指定する方法

<div class="level0">
  <div class="level-1">
    <ul class="level-2">
      <li class="level-3"><p></p></li>
      <li class="level-3"><p class="targetElement"></p></li>
    </ul>
  </div>
</div>

例えば上記のようなサンプルがあるとするとき、「div.level0」を基準として直近の「p.tagetElement」を取得して何かしらの操作を行う場合、次のようにソースを書くことが出来ます。

let target = document.getElementsByClassName('level0')[0].querySelectorAll('.targetElement');
console.log(target);

もしかしたら初心者の方には分かりにくいかもしれませんので、もう少し問題を切り分けて以下のようにすることも可能です。

let ansestor = document.getElementsByClassName('level0')[0]; // 意図的にansestorsではなくansestorにしています
let targets = ansestor.querySelectorAll('.targetElement'); // 意図的にtargetを複数形にしています
console.log(targets);

簡単ですね。「level0」を起点としてquerySelectorAllを使用して直近の子孫要素を取得しています。
もし、getElementsByClassName('level0')[0]の「[0]」が分からない場合は、closest()の記事を参考にしてみてください。

とくに複雑でもないのでこの形を頭に置いておけばとりあえずはなんとかなりますね。ただし、非常に重要な盲点が1つありますので詳しく解説します。

querySelectorAll()で指定できるのは直近の子孫「たち」

単刀直入に言うと、querySelectorAll()メソッドで取得できるのはElementそれ自身ではなく、「該当するエレメントが格納されている配列」です。

どういうことか?次のサンプルを見てください。

<div class="level0">
  <div class="level-1">
    <ul class="level-2">
      <li class="level-3"><p class="targetElement"></p></li><!-- targetElementクラス付与 -->
      <li class="level-3"><p class="targetElement"></p></li>
    </ul>
  </div>
</div>

この例では先ほどと違い、「p.targetElement」が2つあります。どちらも「div.level0」を起点とすると”直近の”「.targetElement」になり、両方が該当します。

ここからがミソで、単に取得したものにクラスを付与したり、中身のテキストを指定したりしても上手くいきません。そこで必要となってくるのがループ処理です。次の例を見てください。

let ansestor = document.getElementsByClassName('level0')[0];
let targets = ansestor.querySelectorAll('.targetElement');
console.log(targets);

// 実はtargetsの中身は配列 [ p.targetElement, p.targetElement ] なんです。

これを、例えば先頭のp.targetElementだけ指定したい場合には、[0]を、2番目のp.targetElementにを指定したい場合は[1]を付けないといけません。

let ansestor = document.getElementsByClassName('level0')[0];
let targets = ansestor.querySelectorAll('.targetElement');

console.log('1:'+ targets[0]); // 先頭の要素
console.log('2:'+ targets[1]); // 2番目の要素

が、厄介なことに該当するエレメントが10個あったりすると目視で[8]とか入れていくのは非効率です。そこでループの出番です。ではサンプルをもとに、1番目のp.targetElementにクラスを付与してみましょう。

直近の子孫「たち」の1つにクラス名を付与したい場合

<div class="level0">
  <div class="level-1">
    <ul class="level-2">
      <li class="level-3"><p class="targetElement"></p></li>
      <li class="level-3"><p class="targetElement"></p></li>
    </ul>
  </div>
</div>

<script>
let ansestor = document.getElementsByClassName('level0')[0];
let targets = ansestor.querySelectorAll('.targetElement');

targets.forEach(function(elem, index){ // ループ処理。forEachです。each()じゃないです。
  if(index == 0){
    elem.classList.add('newClass');  // addClass()としないようにjQueryではないので。
  } else {}
});
</script>

注意点は2つあります。ループにはforEach()を使っていますが、each()jQueryなので使えません。もしくはfor()を使っても良いです。forEach()に関しての記事each()に関しての記事も参考にしてみてください。

もう一つはjQueryで頻出のaddClass()も使えませんのでご注意ください。

子孫要素に特定の要素が存在するか判別する場合

<div class="level0">
  <div class="level-1">
    <ul class="level-2">
      <li class="level-3"><p></p></li>
      <li class="level-3"><p class="targetElement"></p></li>
    </ul>
  </div>
</div>

<script>
let ansestor = document.getElementsByClassName('level0')[0];
let targets = ansestor.querySelectorAll('.targetElement');

if(targets.length == 0){
  // ここに該当がない時の処理
} else {
  // ここに該当がある場合の処理
}
</script>

便宜的にp.targetElementを1つに戻しました。子孫要素にp.targetElementがあるかどうかは、targets.lengthが0かそうでないかで分けるのがベターでしょう。querySelectorAll()で取得した要素は配列に入って返されるので、その配列が空か空でないかで判別するとよいです。

querySelectorAll()についてのまとめ

先祖要素を指定するclosest()とは反対にquerySelectorAll()は子孫要素を指定するメソッドです。

querySelector()やquerySelectorAll()で取得することでDOM操作が可能になります。

判別はquerySelectorAll()で返ってきた配列の中身が空かどうかで行うと良いでしょう。全然難しいことはないので2,3度この記事を読めば理解できるかと思います。

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