Trova l'elemento antenato più vicino che ha una classe specifica


225

Come posso trovare l'antenato di un elemento più vicino all'albero che ha una classe particolare, in puro JavaScript ? Ad esempio, in un albero come questo:

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

Quindi voglio div.near.ancestorse provo questo su pe cerco ancestor.


1
Si prega di notare che il div esterno non è un genitore, è un antenato al pelemento. Se in realtà vuoi solo ottenere il nodo genitore, puoi farlo ele.parentNode.
Felix Kling

@FelixKling: non conoscevo quella terminologia; Lo cambierò.
rvighne,

3
In realtà è lo stesso che usiamo noi umani :) Il padre (genitore) di tuo padre (genitore) non è tuo padre (genitore), è tuo nonno (nonno), o più in generale, il tuo antenato.
Felix Kling

Risposte:


346

Aggiornamento: ora supportato nella maggior parte dei browser principali

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

Nota che questo può corrispondere ai selettori, non solo alle classi

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


Per i browser legacy che non supportano closest()ma hanno matches()uno in grado di creare una corrispondenza dei selettori simile alla corrispondenza di classe di @ rvighne:

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

1
Ancora non supportato nelle versioni correnti di Internet Explorer, Edge e Opera Mini.
Kleinfreund,

1
@kleinfreund - ancora non supportato in IE, Edge o Opera mini. caniuse.com/#search=closest
evolutionxbox

8
Giugno 2016: sembra che tutti i browser non si siano ancora raggiunti
Kunal,

3
Esiste un polifill DOM livello 4 con closestmolte altre funzionalità di attraversamento DOM avanzate. Puoi eseguire tale implementazione da solo o includere l'intero pacchetto per ottenere molte nuove funzionalità nel tuo codice.
Garbee,

2
Edge 15 ha il supporto, che dovrebbe coprire tutti i principali browser. A meno che non conti IE11, ma che non riceva alcun aggiornamento
8472

195

Questo fa il trucco:

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

Il ciclo while attende fino a quando non elha la classe desiderata e imposta elsu elparent ogni iterazione, quindi alla fine hai l'antenato con quella classe o null.

Ecco un violino , se qualcuno vuole migliorarlo. Non funzionerà con i vecchi browser (cioè IE); vedere questa tabella di compatibilità per classList . parentElementviene utilizzato qui perché parentNoderichiederebbe più lavoro per assicurarsi che il nodo sia un elemento.


1
Per un'alternativa a .classList, vedi stackoverflow.com/q/5898656/218196 .
Felix Kling

1
Ho corretto il codice, ma avrebbe comunque generato un errore se non esiste un antenato con tale nome di classe.
Felix Kling

2
Pensavo non esistesse, ma mi sbagliavo . Comunque, parentElementè una proprietà piuttosto nuova (livello DOM 4) ed parentNodeesiste da ... per sempre. Quindi parentNodefunziona anche con browser meno recenti, mentre parentElementpotrebbe non esserlo. Ovviamente potresti fare lo stesso argomento a favore / contro classList, ma non ha una semplice alternativa, come parentElementha fatto. In realtà mi chiedo perché parentElementesista affatto, non sembra aggiungere alcun valore parentNode.
Felix Kling

1
@FelixKling: appena modificato, ora funziona perfettamente nel caso in cui non esistano tali antenati. Devo essermi ecceduto con la versione originale corta.
rvighne,

3
Se vuoi controllare il nome della classe nell'elemento parametro stesso, prima di cercare i suoi antenati, puoi semplicemente cambiare l'ordine delle condizioni nel ciclo:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak

34

Usa element.closest ()

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

Vedi questo DOM di esempio:

<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>

Ecco come useresti 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

14

Sulla base della risposta the8472 e https://developer.mozilla.org/en-US/docs/Web/API/Element/matches ecco la soluzione multipiattaforma 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;
}

11

La soluzione @rvighne funziona bene, ma come identificato nei commenti ParentElemented ClassListentrambi hanno problemi di compatibilità. Per renderlo più compatibile, ho usato:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodeproprietà anziché parentElementproprietà
  • indexOfmetodo sulla classNameproprietà anziché il containsmetodo sulla classListproprietà.

Ovviamente, indexOf sta semplicemente cercando la presenza di quella stringa, non gli importa se è l'intera stringa o no. Quindi, se avessi un altro elemento con classe 'tipo antenato', tornerebbe comunque ad aver trovato 'antenato', se questo è un problema per te, forse puoi usare regexp per trovare una corrispondenza esatta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.