clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

jQuery's .closest() function, and pure JavaScript alternatives

Filed under: JavaScript— Tagged with: jquery, DOM

Here’s a boil-down on the jQuery’s .closest() method and 2 native JavaScript alternatives.

jQuery’s .closest() function is great! Here’s what it does:

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, the closest function has two things:

  1. A starting point — start from here and traverse the DOM up.
  2. The element to look for — stop the traverse when hit this element and return it.

Here's our example markup that we'll be working with:

<div>
  <nav>
    <ul>
      <li class="nav-item-1">item1</li>
      <li class="nav-item-2">item2</li>
      <li class="nav-item-3">item3</li>
      <li class="nav-item-4">item4</li>
    </ul>
  </nav>
</div>

Imagine you want to do something to the nav, but your 'starting point' is one of the li elements (for whatever reason). You might be tempted to do this:

$('.nav-item-1')
  .parent() // ul
  .parent() // nav
  .parent() // div
  .css('background-color', 'red')

That's not too bad, but what if you need to traverse longer distance? Or if you add a wrapper there you need to update the code. Closest is made just for these kinds of thing:

$('.nav-item-1').closest('nav').css('background-color', 'red')

You give the starting point $('.nav-item-1') and the ending point .closest('nav'), and it will traverse the DOM up until it hits the target and returns it.

Pure JavaScript closestByClass function

Below is vanilla JavaScript function that gets the closest by class name. From it, you can see much more in detail how the closest function really works:

var closestByClass = function (el, clazz) {
  // Traverse the DOM up with a while loop
  while (el.className != clazz) {
    // Increment the loop to the parent node
    el = el.parentNode
    if (!el) {
      return null
    }
  }

  // At this point, the while loop has stopped and `el` represents the element
  // that has the class you specified in the second parameter of the function
  // `clazz`
  return el
}

Example usage:

document.onclick = function (e) {
  if (closestByClass(e.target, 'some-class')) {
    // Do something
  } else {
    // Do something else
  }
}

That's nice and all, but it only takes class names, it's easy to make it check for ID, just replace the className with id. Where as it might be totally sufficient in majority of use cases, it's still not very versatile.

Recursive pure JavaScript closest

Here's a more flexible function that uses recursion and is a bit shorter:

var closest = function (el, fn) {
  return el && (fn(el) ? el : closest(el.parentNode, fn))
}

Or write it as one-liner with an arrow function:

var closest = (el, fn) => el && (fn(el) ? el : closest(el.parentNode, fn))

That's quite dense. Let's look at the usage with the example list we used earlier. Note how easy it is to target class, id, or tag name:

var srcEl = document.getElementsByClassName('nav__item-3')

// The element with a class of `.nav` is the wanted element in this case
var nav = closest(srcEl[0], function (el) {
  // Here's the beauty of this function, we have control on the target, here
  // we're using class name `.nav`
  return el.className === 'nav'
})

// Now the variable `nav` contains the closest element with a class `.nav`
console.log(nav)

// Here the target is given as id `#nav-id`
var nav = closest(srcEl[0], el => el.id === 'nav-id')

// Here it's the tag `<nav>`, and so on...
var nav = closest(srcEl[0], el => el.tagName.toLowerCase() === 'nav')

Now the nav variable is set to the closest element, matched with class name, id, or tag name. It's just a regular old reference to a DOM element, anything can be done with it.

This method is unbelievably light and versatile! It originates to this great SO answer.

Here's a little demo.

Pure JavaScript closest demo

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @hiljaa if you want to make a correction etc.

  • © 2021 Antti Hiljá
  • About
  • Follow me in Twatter → @hiljaa
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • console.log('Smash the patriarchy!')
  • I love u!