Temukan elemen leluhur terdekat yang memiliki kelas tertentu

225

Bagaimana saya bisa menemukan leluhur elemen yang paling dekat dengan pohon yang memiliki kelas tertentu, dalam JavaScript murni ? Misalnya, di pohon seperti ini:

<div class="far ancestor">
    <div class="near ancestor">
        <p>Where am I?</p>
    </div>
</div>

Maka saya ingin div.near.ancestorjika saya mencoba ini pdan mencari ancestor.

rvighne
sumber
1
perlu diketahui bahwa div luar tidak orangtua, ini merupakan nenek moyang ke pelemen. Jika Anda benar-benar hanya ingin mendapatkan simpul induk, Anda bisa melakukannya ele.parentNode.
Felix Kling
@ Feliksling: Tidak tahu terminologi itu; Aku akan mengubahnya.
rvighne
3
Ini sebenarnya sama dengan yang kita manusia gunakan :) Ayah (orang tua) ayahmu (orang tua) bukan ayahmu (orang tua), itu kakekmu (kakek nenek), atau lebih umum, leluhurmu.
Felix Kling

Jawaban:

346

Pembaruan: Sekarang didukung di sebagian besar browser utama

document.querySelector("p").closest(".near.ancestor")

Perhatikan bahwa ini dapat mencocokkan penyeleksi, bukan hanya kelas

https://developer.mozilla.org/en-US/docs/Web/API/Element.closest


Untuk peramban lawas yang tidak mendukung closest()tetapi memilikinya, matches()dapat membangun pencocokan pemilih yang mirip dengan pencocokan kelas @ rvighne:

function findAncestor (el, sel) {
    while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
    return el;
}
the8472
sumber
1
Masih tidak didukung di versi Internet Explorer, Edge dan Opera Mini saat ini.
kleinfreund
1
@kleinfreund - masih belum didukung di IE, Edge, atau Opera mini. caniuse.com/#search=closest
evolutionxbox
8
Juni 2016: Sepertinya semua browser belum menyusul
Kunal
3
Ada DOM Level 4 polyfill dengan closestbanyak fitur traversal DOM yang lebih canggih. Anda dapat menarik implementasi itu sendiri atau menyertakan seluruh bundel untuk mendapatkan banyak fitur baru dalam kode Anda.
Garbee
2
Edge 15 memiliki dukungan, yang seharusnya mencakup semua browser utama. Kecuali Anda menghitung IE11, tetapi itu tidak menerima pembaruan apa pun
the8472
195

Ini caranya:

function findAncestor (el, cls) {
    while ((el = el.parentElement) && !el.classList.contains(cls));
    return el;
}

Loop sementara menunggu hingga elmemiliki kelas yang diinginkan, dan mengatur elke elorangtua setiap iterasi sehingga pada akhirnya, Anda memiliki leluhur dengan kelas itu atau null.

Ini biola , jika ada yang ingin memperbaikinya. Ini tidak akan berfungsi pada browser lama (yaitu IE); lihat tabel kompatibilitas ini untuk classList . parentElementdigunakan di sini karena parentNodeakan melibatkan lebih banyak pekerjaan untuk memastikan bahwa simpul adalah elemen.

rvighne
sumber
1
Untuk alternatifnya .classList, lihat stackoverflow.com/q/5898656/218196 .
Felix Kling
1
Saya memperbaiki kodenya, tetapi masih akan menimbulkan kesalahan jika tidak ada leluhur dengan nama kelas seperti itu.
Felix Kling
2
Eh, kupikir itu tidak ada, tapi aku salah . Bagaimanapun, parentElementadalah properti yang agak baru (DOM level 4) dan parentNodeada sejak ... selamanya. Begitu parentNodejuga bekerja di browser lama, sedangkan parentElementmungkin tidak. Tentu saja Anda dapat membuat argumen yang sama untuk / melawan classList, tetapi tidak memiliki alternatif yang sederhana, seperti yang parentElementdimiliki. Saya benar-benar bertanya-tanya mengapa parentElementada sama sekali, sepertinya tidak menambah nilai lebih parentNode.
Felix Kling
1
@ Feliksling: Baru diedit, sekarang berfungsi dengan baik untuk kasus ketika tidak ada leluhur seperti itu. Saya pasti terlalu bersemangat dengan versi aslinya yang pendek.
rvighne
3
Jika Anda ingin memeriksa nama kelas dalam elemen parameter itu sendiri, sebelum mencari leluhurnya, Anda dapat dengan mudah mengubah urutan kondisi dalam loop:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak
34

Gunakan element.closest ()

https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Lihat contoh ini DOM:

<article>
  <div id="div-01">Here is div-01
    <div id="div-02">Here is div-02
      <div id="div-03">Here is div-03</div>
    </div>
  </div>
</article>

Ini adalah bagaimana Anda akan menggunakan element.closest:

var el = document.getElementById('div-03');

var r1 = el.closest("#div-02");  
// returns the element with the id=div-02

var r2 = el.closest("div div");  
// returns the closest ancestor which is a div in div, here is div-03 itself

var r3 = el.closest("article > div");  
// returns the closest ancestor which is a div and has a parent article, here is div-01

var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article
simbro
sumber
14

Berdasarkan jawaban the8472 dan https://developer.mozilla.org/en-US/docs/Web/API/Element/matches di sini adalah solusi cross-platform 2017:

if (!Element.prototype.matches) {
    Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {}
            return i > -1;
        };
}

function findAncestor(el, sel) {
    if (typeof el.closest === 'function') {
        return el.closest(sel) || null;
    }
    while (el) {
        if (el.matches(sel)) {
            return el;
        }
        el = el.parentElement;
    }
    return null;
}
marverix
sumber
11

Solusi @rvighne bekerja dengan baik, tetapi seperti yang diidentifikasi dalam komentar ParentElementdan ClassListkeduanya memiliki masalah kompatibilitas. Untuk membuatnya lebih kompatibel, saya telah menggunakan:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodeproperti bukan parentElementproperti
  • indexOfmetode di classNameproperti bukan containsmetode di classListproperti.

Tentu saja, indexOf hanya mencari keberadaan string itu, tidak peduli apakah itu keseluruhan string atau tidak. Jadi jika Anda memiliki elemen lain dengan kelas 'leluhur-jenis' itu masih akan kembali sebagai telah menemukan 'leluhur', jika ini merupakan masalah bagi Anda, mungkin Anda dapat menggunakan regexp untuk menemukan pasangan yang tepat.

Josh
sumber