DOM Traversal: closest() in Vanilla JavaScript and jQuery

Hero image for DOM Traversal: closest() in Vanilla JavaScript and jQuery. Image by Jackson So.
Hero image for 'DOM Traversal: closest() in Vanilla JavaScript and jQuery.' Image by Jackson So.

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 dropdown 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 mobilesubmenuactive 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 returns null.

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 nonexistent.

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 futureproof 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.


Categories:

  1. Development
  2. Front‑End Development
  3. JavaScript
  4. jQuery