web-dev-qa-db-fra.com

Trouver l'élément ancêtre le plus proche ayant une classe spécifique

Comment puis-je trouver l'ancêtre d'un élément le plus proche de l'arbre ayant une classe particulière, en JavaScript pur? Par exemple, dans un arbre comme celui-ci:

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

Ensuite, je veux div.near.ancestor si je l’essaye sur p et que je cherche ancestor.

177
rvighne

Mise à jour: Maintenant supporté dans la plupart des navigateurs principaux

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

Notez que cela peut correspondre aux sélecteurs, pas seulement aux classes

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


Pour les anciens navigateurs qui ne prennent pas en charge closest() mais qui ont matches(), vous pouvez créer une correspondance de sélecteur similaire à celle de @ rvighne:

function findAncestor (el, sel) {
    while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
    return el;
}
265
the8472

Cela fait le tour:

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

La boucle while attend que el ait la classe désirée et règle el sur le parent de el à chaque itération. Vous avez donc l'ancêtre avec cette classe ou null .

Voici un violon , si quelqu'un veut l'améliorer. Cela ne fonctionnera pas sur les anciens navigateurs (IE); voir ceci tableau de compatibilité pour classList . parentElement est utilisé ici parce que parentNode impliquerait davantage de travail pour s'assurer que le nœud est un élément.

185
rvighne

Utilisez element.closest ()

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

Voir cet exemple 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>

Voici comment utiliser 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
24
simbro

Basé sur le the8472 answer et https://developer.mozilla.org/en-US/docs/Web/API/Element/matches voici la solution 2017 multiplate-forme :

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;
}
11
marverix

La solution @rvighne fonctionne bien, mais comme indiqué dans les commentaires, ParentElement et ClassList présentent tous deux des problèmes de compatibilité. Pour le rendre plus compatible, j'ai utilisé:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • Propriété parentNode à la place de la propriété parentElement
  • Méthode indexOf sur la propriété className à la place de la méthode contains sur la propriété classList.

Bien sûr, indexOf recherche simplement la présence de cette chaîne, peu importe si c'est la chaîne entière ou non. Donc, si vous avez un autre élément avec la classe 'ancêtre-type', il retournera toujours comme ayant trouvé 'ancêtre'. Si cela vous pose problème, vous pouvez peut-être utiliser regexp pour trouver une correspondance exacte.

9
Josh