Найдите ближайший элемент предка, который имеет определенный класс

225

Как я могу найти предка элемента, который находится ближе всего к дереву, имеющему определенный класс, в чистом JavaScript ? Например, в дереве так:

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

Тогда я хочу, div.near.ancestorесли я попробую это на pи искать ancestor.

rvighne
источник
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;
}
the8472
источник
1
По-прежнему не поддерживается в текущих версиях Internet Explorer, Edge и Opera Mini.
Kleinfreund
1
@kleinfreund - до сих пор не поддерживается в IE, Edge или Opera mini. caniuse.com/#search=closest
evolutionxbox
8
Июнь 2016: похоже, что все браузеры до сих пор не догнали
Кунал
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;
}

Цикл while ждет, пока elне получит желаемый класс, и он устанавливает elв качестве elродителя каждую итерацию, так что в итоге у вас есть предок с этим классом или null.

Вот скрипка , если кто-то хочет ее улучшить. Это не будет работать на старых браузерах (т.е. IE); см. эту таблицу совместимости для classList . parentElementиспользуется здесь, потому parentNodeчто потребуется больше работы, чтобы убедиться, что узел является элементом.

rvighne
источник
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
simbro
источник
Я вижу, что все браузеры, кроме Internet Explorer, теперь поддерживают «самые близкие» (июнь 2020 года).
user2677034
14

На основе ответа 8472 и 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;
}
marverix
источник
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 просто ищет наличие этой строки, ему все равно, вся эта строка или нет. Поэтому, если у вас есть другой элемент с классом 'ancestor-type', он все равно вернется, если нашел 'ancestor', если это проблема для вас, возможно, вы можете использовать regexp, чтобы найти точное совпадение.

мистифицировать
источник