查找具有特定类的最接近的祖先元素


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
Felix Kling 2014年

@FelixKling:不知道该术语;我会改变它。
rvighne 2014年

3
实际上,这与我们人类使用的相同:)您父亲(父母)的父亲(父母)不是您父亲(父母),而是您的祖父(祖父母),或更一般地说,是您的祖先。
Felix Kling 2014年

Answers:


346

更新:大多数主流浏览器现在都支持

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

请注意,这可以匹配选择器,而不仅仅是类

https://developer.mozilla.org/zh-CN/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年6月:似乎所有浏览器仍未赶上发展
Kunal

3
有一个DOM Level 4 polyfill,其中包含closest许多更高级的DOM遍历功能。您可以单独实现该实现,也可以包括整个捆绑包,以在代码中获得许多新功能。
Garbee

2
Edge 15具有支持,应该涵盖所有主要的浏览器。除非您算上IE11,否则不会收到任何更新
the8472 '17

195

这可以解决问题:

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

while循环等待直到el拥有所需的类,并且每次迭代都将其设置elel的父类,因此最后,您的祖先拥有该类或null

如果有人想改进它,这是一个小提琴。它不能在旧的浏览器(即IE)上运行;有关classList的信息,请参见此兼容性表parentElement之所以在此处使用,是因为parentNode会涉及更多工作,以确保该节点是一个元素。


1
有关替代方法.classList,请参见stackoverflow.com/q/5898656/218196
Felix Kling 2014年

1
我修复了代码,但是如果没有祖先这样的类名,它仍然会引发错误。
Felix Kling 2014年

2
呃,我以为它不存在,但是我错了。无论如何,这parentElement是一个相当新的属性(DOM级别4),并且parentNode从...一直存在。所以parentNode在较旧的浏览器中也可以,但是parentElement可能不行。当然,您可以针对/ against输入相同的参数classList,但它没有parentElementhas 那样的简单替代方法。我实际上想知道为什么parentElement存在,它似乎并没有增加任何价值parentNode
Felix Kling 2014年

1
@FelixKling:刚刚编辑,现在可以在没有这样祖先的情况下正常工作。我一定对简短的原始版本过于热衷。
rvighne 2014年

3
如果要在参数元素本身中检查类名,则可以在搜索其祖先之前简单地在循环中切换条件的顺序:while (!el.classList.contains(cls) && (el = el.parentElement));
Nicomak

34

使用element.closest()

https://developer.mozilla.org/zh-CN/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解决方案效果很好,但正如注释中所指出的ParentElementClassList两者都存在兼容性问题。为了使其更加兼容,我使用了:

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来查找完全匹配。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.