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

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

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

Затем я хочу div.near.ancestor, если я попробую это на p и выполните поиск ancestor.

Ответ 1

Это трюк:

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

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

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

Ответ 2

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

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

Ответ 3

Используйте 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

Ответ 4

На основе ответа 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;
}

Ответ 5

Решение

@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 просто ищет наличие этой строки, все равно, является ли она всей строкой или нет. Поэтому, если у вас есть еще один элемент с классом "тип предка", он все равно вернется, поскольку нашел "предка", если это проблема для вас, возможно, вы можете использовать regexp для поиска точного соответствия.