Suchen Sie das nächstgelegene Vorfahrenelement mit einer bestimmten Klasse

224

Wie kann ich in reinem JavaScript den Vorfahren eines Elements finden, der dem Baum mit einer bestimmten Klasse am nächsten liegt ? Zum Beispiel in einem Baum wie diesem:

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

Dann möchte div.near.ancestorich, wenn ich das probiere pund suche ancestor.

rvighne
quelle
1
Bitte beachten Sie, dass das äußere Div kein Elternteil ist, sondern ein Vorfahr des pElements. Wenn Sie tatsächlich nur den übergeordneten Knoten erhalten möchten, können Sie dies tun ele.parentNode.
Felix Kling
@ FelixKling: Wusste diese Terminologie nicht; Ich werde es ändern.
Rvighne
3
Es ist eigentlich das gleiche wie wir Menschen :) Der Vater (Elternteil) Ihres Vaters (Elternteil) ist nicht Ihr Vater (Elternteil), es ist Ihr Großvater (Großelternteil) oder allgemeiner Ihr Vorfahr.
Felix Kling

Antworten:

343

Update: Wird jetzt in den meisten gängigen Browsern unterstützt

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

Beachten Sie, dass dies mit Selektoren übereinstimmen kann, nicht nur mit Klassen

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


Für ältere Browser, die keine Unterstützung bieten, closest()aber über matches()eine solche verfügen , kann eine Selektoranpassung erstellt werden, die der Klassenanpassung von @ rvighne ähnelt:

function findAncestor (el, sel) {
    while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
    return el;
}
the8472
quelle
1
Wird in den aktuellen Versionen von Internet Explorer, Edge und Opera Mini immer noch nicht unterstützt.
Kleinfreund
1
@kleinfreund - wird in IE, Edge oder Opera mini immer noch nicht unterstützt. caniuse.com/#search=closest
evolutionxbox
8
Juni 2016: Sieht so aus, als hätten alle Browser noch nicht aufgeholt
Kunal
3
Es gibt eine DOM Level 4- Polyfüllung mit closestvielen erweiterten DOM-Traversal-Funktionen. Sie können diese Implementierung selbst abrufen oder das gesamte Bundle einschließen, um viele neue Funktionen in Ihren Code aufzunehmen.
Garbee
2
Edge 15 bietet Unterstützung, die alle gängigen Browser abdecken sollte. Es sei denn, Sie zählen IE11, aber das erhält keine Updates
the8472
195

Das macht den Trick:

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

Die while-Schleife wartet, bis elsie die gewünschte Klasse hat, und setzt elbei eljeder Iteration auf 's übergeordnetes Element, sodass Sie am Ende den Vorfahren mit dieser Klasse oder haben null.

Hier ist eine Geige , wenn jemand sie verbessern will. Es funktioniert nicht mit alten Browsern (z. B. IE). In dieser Kompatibilitätstabelle finden Sie Informationen zu classList . parentElementwird hier verwendet, da parentNodemehr Arbeit erforderlich wäre, um sicherzustellen, dass der Knoten ein Element ist.

rvighne
quelle
1
Eine Alternative dazu .classListfinden Sie unter stackoverflow.com/q/5898656/218196 .
Felix Kling
1
Ich habe den Code behoben, aber es würde immer noch einen Fehler auslösen, wenn es keinen Vorfahren mit einem solchen Klassennamen gibt.
Felix Kling
2
Äh, ich dachte es existiert nicht, aber ich habe mich geirrt . Wie auch immer, parentElementist eine ziemlich neue Eigenschaft (DOM Level 4) und parentNodeexistiert seit ... für immer. Funktioniert also parentNodeauch in älteren Browsern, wohingegen dies parentElementmöglicherweise nicht der Fall ist . Natürlich könnten Sie das gleiche Argument für / gegen vorbringen classList, aber es gibt keine einfache Alternative wie parentElementzuvor. Ich frage mich eigentlich, warum parentElementes überhaupt existiert, es scheint keinen Mehrwert zu bieten parentNode.
Felix Kling
1
@FelixKling: Gerade bearbeitet, funktioniert es jetzt gut für den Fall, dass kein solcher Vorfahr existiert. Ich muss mit der kurzen Originalversion übereifrig geworden sein.
Rvighne
3
Wenn Sie den Klassennamen im Parameterelement selbst überprüfen möchten, bevor Sie seine Vorfahren durchsuchen, können Sie einfach die Reihenfolge der Bedingungen in der Schleife while (!el.classList.contains(cls) && (el = el.parentElement));
ändern
34

Verwenden Sie element.closest ()

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

Siehe dieses Beispiel 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>

So würden Sie element.closest verwenden:

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

Basierend auf der Antwort von the8472 und https://developer.mozilla.org/en-US/docs/Web/API/Element/matches ist hier eine plattformübergreifende Lösung für 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
quelle
11

@rvighne Lösung funktioniert gut, aber wie in den Kommentaren identifiziert ParentElementund ClassListbeide haben Kompatibilitätsprobleme. Um es kompatibler zu machen, habe ich verwendet:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodeEigentum anstelle des parentElementEigentums
  • indexOfMethode auf der classNameEigenschaft anstelle der containsMethode auf der classListEigenschaft.

Natürlich sucht indexOf einfach nach dem Vorhandensein dieser Zeichenfolge. Es ist egal, ob es sich um die gesamte Zeichenfolge handelt oder nicht. Wenn Sie also ein anderes Element mit der Klasse 'Ahnen-Typ' hätten, würde es immer noch als 'Ahnen' gefunden zurückkehren. Wenn dies ein Problem für Sie ist, können Sie möglicherweise Regexp verwenden, um eine genaue Übereinstimmung zu finden.

Josh
quelle