Знайдіть найближчий елемент предка, який має певний клас


225

Як я можу знайти предка елемента, який знаходиться найближче до дерева, яке має певний клас, у чистому JavaScript ? Наприклад, у такому дереві:

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

Тоді мені хочеться, div.near.ancestorякщо я спробую це pі шукаю ancestor.


1
Будь ласка , зверніть увагу , що зовнішній DIV не є батьком, що це предок до pелементу. Якщо ви насправді хочете отримати лише батьківський вузол, ви можете це зробити ele.parentNode.
Фелікс Клінг

@FelixKling: не знав цієї термінології; Я його зміню.
rvighne

3
Це насправді те саме, що ми використовуємо люди :) Батько (батько) твого батька (батька) - це не твій батько (батько), це твій дід (бабуся і дідусь), або, взагалі кажучи, твій предк.
Фелікс Клінг

Відповіді:


346

Оновлення: тепер підтримується в більшості основних браузерів

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

Зауважте, що це може відповідати селекторам, а не лише класам

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


Для застарілих веб-переглядачів, які не підтримують, closest()але вони matches()можуть створювати відповідність селектору, подібну до зіставлення класу @ rvighne:

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

1
Досі не підтримується в поточних версіях Internet Explorer, Edge та Opera Mini.
kleinfreund

1
@kleinfreund - все ще не підтримується в IE, Edge або Opera mini. caniuse.com/#search=closest
evolutionxbox

8
Червень 2016: Схоже, усі браузери досі не наздогнали
Kunal

3
Існує полімер DOM рівня 4 з closestдодатковою кількістю вдосконалених функцій проходу DOM. Ви можете виконати цю реалізацію самостійно або включити весь пакет, щоб отримати багато нових функцій у своєму коді.
Гарбі

2
Edge 15 має підтримку, яка повинна охоплювати всі основні браузери. Якщо ви не порахуєте IE11, але він не отримає жодних оновлень
the8472

195

Це робить трюк:

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

Під час очікування циклу до тих пір , elпоки потрібний клас, і він встановлює elв elбатьківському кожну ітерацію так , в кінці кінців, у вас є предок цього класу або null.

Ось загадка , якщо хтось хоче її вдосконалити. Він не працюватиме на старих браузерах (тобто IE); див. цю таблицю сумісності для ClassList . parentElementтут використовується, оскільки parentNodeпередбачає більше роботи, щоб переконатися, що вузол є елементом.


1
Для альтернативи .classListдив. Stackoverflow.com/q/5898656/218196 .
Фелікс Клінг

1
Я виправив код, але він все одно призведе до помилки, якщо немає предка з таким іменем класу.
Фелікс Клінг

2
Ага, я думав, що це не існує, але я помилявся . У будь-якому випадку, parentElementце досить нова властивість (рівень DOM 4) і parentNodeіснує з… вічно. Так parentNodeсамо працює і в старих браузерах, тоді як parentElementце не може. Звичайно, ви можете зробити той же аргумент за / проти classList, але він не має простої альтернативи, як, наприклад parentElement,. Мені справді цікаво, чому parentElementвзагалі існує, але це, схоже, не додає ніякої цінності parentNode.
Фелікс Клінг

1
@FelixKling: Щойно відредагований, він зараз добре працює у випадку, коли такого предка не існує. Я, мабуть, пережив свою коротку оригінальну версію.
rvighne

3
Якщо ви хочете перевірити ім'я класу в самому елементі параметра, перш ніж шукати його предків, ви можете просто переключити порядок умов у циклі:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak,

34

Використовувати element.closest ()

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

Дивіться цей приклад 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>

Ось як би ви використовували 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

На основі відповіді the8472 та https://developer.mozilla.org/en-US/docs/Web/API/Element/matches ось рішення міжплатформного 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

Рішення @rvighne працює добре, але, як визначено в коментарях, ParentElementі в ClassListобох є проблеми сумісності. Щоб зробити його більш сумісним, я використав:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNodeмайно замість parentElementвласності
  • indexOfметод у classNameвластивості замість containsметоду у classListвластивості.

Звичайно, indexOf просто шукає наявність цього рядка, йому не байдуже, це цілий рядок чи ні. Тож якби у вас був інший елемент з класом 'type-pretstor', він все одно повернеться як знайдений 'предка', якщо це проблема для вас, можливо, ви можете використовувати regexp, щоб знайти точну відповідність.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.