Znajdź najbliższy element przodka, który ma określoną klasę

225

Jak znaleźć przodka elementu najbliższego drzewa, które ma określoną klasę, w czystym JavaScript ? Na przykład w takim drzewie:

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

Więc chcę, div.near.ancestorjeśli spróbuję tego na pi wyszukaj ancestor.

Rvighne
źródło
1
proszę zauważyć, że zewnętrzna div nie jest rodzicem, to jest przodek do pelementu. Jeśli faktycznie chcesz tylko uzyskać węzeł nadrzędny, możesz to zrobić ele.parentNode.
Felix Kling
@FelixKling: Nie znałem tej terminologii; Zmienię to.
rvighne
3
W rzeczywistości jest taki sam, jak my, ludzie używamy :) Ojciec (rodzic) twojego ojca (rodzica) nie jest twoim ojcem (rodzicem), to twoim dziadkiem (dziadkiem), lub bardziej ogólnie mówiąc, twoim przodkiem.
Felix Kling

Odpowiedzi:

346

Aktualizacja: teraz obsługiwane w większości głównych przeglądarek

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

Pamiętaj, że może to pasować do selektorów, a nie tylko klas

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


W przypadku starszych przeglądarek, które nie obsługują, closest()ale mają matches()takie, można zbudować dopasowywanie selektorów podobne do dopasowywania klas @ rvighne:

function findAncestor (el, sel) {
    while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
    return el;
}
the8472
źródło
1
Nadal nie jest obsługiwany w bieżących wersjach Internet Explorera, Edge i Opera Mini.
kleinfreund,
1
@kleinfreund - wciąż nieobsługiwany w IE, Edge lub Opera mini. caniuse.com/#search=closest
evolutionxbox
8
Czerwiec 2016: Wygląda na to, że wszystkie przeglądarki wciąż nie nadrobiły zaległości
Kunal
3
Istnieje polypełniacz DOM Level 4 z closestwieloma bardziej zaawansowanymi funkcjami przejścia DOM. Możesz pobrać tę implementację samodzielnie lub dołączyć cały pakiet, aby uzyskać wiele nowych funkcji w kodzie.
Garbee,
2
Edge 15 ma wsparcie, które powinno obejmować wszystkie główne przeglądarki. O ile nie policzysz IE11, ale to nie otrzyma żadnych aktualizacji
the8472
195

To załatwia sprawę:

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

Pętla while czeka, aż będzie elmiała pożądaną klasę, i ustawia się elna elrodzica przy każdej iteracji, więc ostatecznie masz przodka z tą klasą lub null.

Oto skrzypce , jeśli ktoś chce to poprawić. Nie będzie działać na starych przeglądarkach (tj. IE); zobacz tabelę zgodności dla classList . parentElementjest tutaj używany, ponieważ parentNodewymagałoby to więcej pracy, aby upewnić się, że węzeł jest elementem.

Rvighne
źródło
1
Alternatywę dla .classListmożna znaleźć na stronie stackoverflow.com/q/5898656/218196 .
Felix Kling
1
Naprawiłem kod, ale nadal zgłaszałby błąd, jeśli nie ma przodka o takiej nazwie klasy.
Felix Kling
2
Myślałem, że to nie istnieje, ale się myliłem . Tak czy inaczej, parentElementjest raczej nową własnością (DOM poziom 4) i parentNodeistnieje od… na zawsze. Więc parentNodedziała również w starszych przeglądarkach, parentElementale może nie. Oczywiście możesz podać ten sam argument za / przeciw classList, ale nie ma prostej alternatywy, jak parentElementma. Właściwie zastanawiam się, dlaczego parentElementw ogóle istnieje, nie wydaje się, aby przynosiło to jakąkolwiek wartość parentNode.
Felix Kling
1
@ FelixKling: Właśnie edytowane, teraz działa dobrze w przypadku, gdy nie ma takiego przodka. Musiałem być przesadny z krótką oryginalną wersją.
rvighne
3
Jeśli chcesz sprawdzić nazwę klasy w samym elemencie parametru, przed wyszukaniem jej przodków możesz po prostu zmienić kolejność warunków w pętli:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak
34

Użyj element.closest ()

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

Zobacz ten przykład 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>

Oto jak użyjesz elementu.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
źródło
14

Na podstawie odpowiedzi the8472 i https://developer.mozilla.org/en-US/docs/Web/API/Element/matches tutaj znajduje się międzyplatformowe rozwiązanie 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
źródło
11

Rozwiązanie @rvighne działa dobrze, ale jak stwierdzono w komentarzach ParentElementi ClassListoba mają problemy ze zgodnością. Aby uczynić go bardziej kompatybilnym, użyłem:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodewłaściwość zamiast parentElementwłaściwości
  • indexOfmetoda na classNamewłaściwości zamiast containsmetody na classListwłaściwość.

Oczywiście indexOf szuka po prostu obecności tego łańcucha, nie ma znaczenia, czy jest to cały łańcuch, czy nie. Więc jeśli miałbyś inny element z klasą „typ przodka”, nadal zwróciłby się jako znaleziony „przodek”, jeśli jest to dla ciebie problem, być może możesz użyć wyrażenia regularnego, aby znaleźć dokładne dopasowanie.

Josh
źródło