How to check if an element is a descendant of another

By

Learn how to check if an element is a descendant of another in JavaScript by walking up the parentNode chain until you match the parent element id.

~~~

To check if a DOM element is a descendant of another, walk up its parentNode chain. If you meet the parent element along the way, it’s a descendant. If you reach the top of the document without meeting it, it’s not.

I needed this while handling clicks. I had a click listener on the whole document, and I wanted to know if the clicked element was inside a specific container.

event.target gives you the exact element that was clicked. That could be a span nested three levels deep inside the container, so comparing it directly to the container is not enough.

Walking up the tree

I assigned an id to the container, and I checked the clicked element against it with this function:

const isDescendant = (el, parentId) => {
  let isChild = false

  if (el.id === parentId) { //is this the element itself?
    isChild = true
  }

  while ((el = el.parentNode)) {
    if (el.id === parentId) {
      isChild = true
    }
  }

  return isChild
}

document.addEventListener('click', event => {
  const parentId = 'mycontainer'

  if (isDescendant(event.target, parentId)) {
    //it is a descendant, handle this case here
  } else {
    //it's not a descendant, handle this case here
  }
})

In the while condition we use the assignment operator =, not a comparison. Each iteration moves el one level up the tree. When there’s no parent left, el.parentNode returns null, the condition is falsy, and the loop ends.

It’s a way to go “up” the elements tree until it finishes.

Notice the check before the loop. Without it, clicking directly on the container itself would return false, because the loop starts from the parent. That’s a bug I hit the first time.

One more thing about that assignment inside the condition. Linters flag it, because it looks like a === typo. Wrapping it in an extra pair of parentheses, like I did above, tells the linter (and the next person reading the code) that it’s intentional.

The built-in alternative

If you already have a reference to the parent element, the DOM gives you contains():

const container = document.querySelector('#mycontainer')
container.contains(event.target) //true or false

It returns true for any descendant, and for the element itself. The loop above shows what happens under the hood, and it’s handy when all you have is an id to match against.

~~~

Related posts about js: