
DOM Traversal: closest() in Vanilla JavaScript and jQuery
This Article is Over Eleven Years Old...
Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.
You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.
Traversing the DOM is an essential part of developing interactivity into a website, making it feel more alive. For example, developing the classic drop‑down navigation (and corresponding mobile version). Clicking on a dropdown link or a button to expand a submenu is a pretty common interaction on the web, and every time I've built a mobile menu, it has required some form of DOM traversal and manipulation.
In this post, we're going to take a look at the .closest() method in jQuery, and then delve into some of the ways we can achieve the same functionality with straight, vanilla JavaScript. Thankfully, you'll find it's very straightforward.
.closest() in jQuery
So, first things first, what does .closest() actually do? Here is how the jQuery docs describe it:
“For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
Essentially, it programmatically works its way up through the DOM tree, starting at a specific element (which you've defined) and testing each parent against the selector you're searching for.
This means that you can use interactions on a child element to affect the parent, grandparent, or any other ancestor element, which is incredibly handy.
Let's take a look at it in the context of a mobile menu:
<header> <nav> <ul> <li> <span class="trigger">About Us</span> <ul> <li> <a href="/contact">Contact Us</a> </li> <li> <a href="/our-work">Our Work</a> </li> <li> <a href="/testimonials">Testimonials</a> </li> </ul> </li> </ul> </nav></header><script> $('.trigger').click(function () { $(this).closest('nav').addClass('mobile-submenu-active'); $(this).next('ul').slideToggle(300); });</script>Now, obviously, there is more needed here to make this work nicely and add some finesse, but what we have here is the barebones of how it starts to come together. Using .closest() you can see that clicking to open a submenu applies the mobile‑submenu‑active class to the <nav> element. This additional class gives us the opportunity to apply any styling or CSS animations that we choose to use.
So, it is demonstrably fairly easy to handle this using jQuery but ‑ as I've discussed a few times before ‑ jQuery is a luxury, and oftentimes we don't need to weigh down our projects by including a complete additional library for something that can also be accomplished straightforwardly using pure JavaScript.
.closest() in pure, vanilla JavaScript
Why use a framework when we already have access to the tools to do the job without it? Usually, the benefit of jQuery is its ability to offer a standardised way where browser implementations and support are fragmented. This is very much the case with JavaScript's very own closest() method.
This works in essentially the same way as the jQuery implementation does. According to the MDN Web Docs:
“The
closest()method traverses the Element and its parents (heading toward the document root) until it finds a node that matches the provided selector string. Will return itself or the matching ancestor. If no such element exists, it returnsnull.
And ‑ following the same example as we used above, in action it looks a little like this:
<header> <nav> <ul> <li> <span onclick="mobileSubmenuActive();" id="top-level-span"> About Us </span> <ul> <li> <a href="/contact">Contact Us</a> </li> <li> <a href="/our-work">Our Work</a> </li> <li> <a href="/testimonials">Testimonials</a> </li> </ul> </li> </ul> </nav></header><script> function mobileSubmenuActive() { var topLevelSpan = document.getElementById('top-level-span'); var nav = topLevelSpan.closest('nav'); nav.classList.add('mobile-submenu-active'); }</script>This should all look very familiar: the jQuery version behaves in much the same way, the key difference ‑ as I touched upon a little earlier ‑ is browser support. Internet Explorer is the problem here again: support in IE for the vanilla JS version of closest() is entirely non‑existent.
Thankfully, MDN also offers a polyfill that will allow support for IE9+:
if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;}if (!Element.prototype.closest) { Element.prototype.closest = function (s) { var el = this; do { if (Element.prototype.matches.call(el, s)) return el; el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); return null; };}As you might imagine, this won't be quite as performant as in more modern browsers but it will help future‑proof your code a little. MDN does also present a further workaround for even earlier versions of IE but warns that it could cause huge lag spikes and that it only supports CSS2 selectors so perhaps best avoided.
All in all, I'm a big advocate of removing jQuery wherever possible. As the JavaScript specification continues to mature that becomes easier to achieve. For now, though, where we are caught supporting outdated browsers, it is much harder to achieve without adding more weight to the project's client side.
Related Articles

Using classList in JavaScript: add(), remove(), toggle(), and contains(). Using
How to Check an Element Exists with and Without jQuery. How to Check an Element Exists with and Without jQuery

ParseInt in JavaScript: The Significance of Radix. parseIntin JavaScript: The Significance of Radix
JavaScript's hasOwnProperty() Method. JavaScript's
hasOwnProperty()Method
Best Practices for Cross‑Browser Compatibility. Best Practices for Cross‑Browser Compatibility

Staying Current: Automating Copyright Year Updates. Staying Current: Automating Copyright Year Updates
ReferenceError: Window is Not Defined in Gatsby. ReferenceError: Window is Not Defined in Gatsby

Lifting State up in React. Lifting State up in React

Caching Strategies for Data Fetching in Next.js. Caching Strategies for Data Fetching in Next.js

Exploring the Liquid Templating Language. Exploring the Liquid Templating Language

LeetCode: Finding the Diameter of a Binary Tree. LeetCode: Finding the Diameter of a Binary Tree

Single or Double Colons in CSS Pseudo‑Elements (:before vs. ::before). Single or Double Colons in CSS Pseudo‑Elements (
:beforevs.::before)