Find the closest ancestor element that has a specific class

JavascriptHtmlDom

Javascript Problem Overview


How can I find an element's ancestor that is closest up the tree that has a particular class, in pure JavaScript? For example, in a tree like so:

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

Then I want div.near.ancestor if I try this on the p and search for ancestor.

Javascript Solutions


Solution 1 - Javascript

Update: Now supported in most major browsers

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

Note that this can match selectors, not just classes

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


For legacy browsers that do not support closest() but have matches() one can build selector-matching similar to @rvighne's class matching:

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

Solution 2 - Javascript

This does the trick:

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

The while loop waits until el has the desired class, and it sets el to el's parent every iteration so in the end, you have the ancestor with that class or null.

Here's a fiddle, if anyone wants to improve it. It won't work on old browsers (i.e. IE); see this compatibility table for classList. parentElement is used here because parentNode would involve more work to make sure that the node is an element.

Solution 3 - Javascript

Use element.closest()

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

See this example 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>

This is how you would use 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

Solution 4 - Javascript

Based on the the8472 answer and https://developer.mozilla.org/en-US/docs/Web/API/Element/matches here is cross-platform 2017 solution:

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

Solution 5 - Javascript

@rvighne solution works well, but as identified in the comments ParentElement and ClassList both have compatibility issues. To make it more compatible, I have used:

function findAncestor (el, cls) {
    while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
    return el;
}
  • parentNode property instead of the parentElement property
  • indexOf method on the className property instead of the contains method on the classList property.

Of course, indexOf is simply looking for the presence of that string, it does not care if it is the whole string or not. So if you had another element with class 'ancestor-type' it would still return as having found 'ancestor', if this is a problem for you, perhaps you can use regexp to find an exact match.

Solution 6 - Javascript

This solution should work for IE9 and up.

It's like jQuery's parents() method when you need to get a parent container which might be up a few levels from the given element, like finding the containing <form> of a clicked <button>. Looks through the parents until the matching selector is found, or until it reaches the <body>. Returns either the matching element or the <body>.

function parents(el, selector){
	var parent_container = el;
	do {
		parent_container = parent_container.parentNode;
	}
	while( !parent_container.matches(selector) && parent_container !== document.body );

	return parent_container;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrvighneView Question on Stackoverflow
Solution 1 - Javascriptthe8472View Answer on Stackoverflow
Solution 2 - JavascriptrvighneView Answer on Stackoverflow
Solution 3 - JavascriptsimbroView Answer on Stackoverflow
Solution 4 - JavascriptmarverixView Answer on Stackoverflow
Solution 5 - JavascriptJoshView Answer on Stackoverflow
Solution 6 - JavascriptJamesView Answer on Stackoverflow