skip to Main Content

I want to write a snippet of code that will traverse the document.body of a page and report all empty block level DOM nodes. This is a pass for SEO/a11y.

I found this article that will seemingly help:

https://css-tricks.com/snippets/jquery/check-for-empty-elements/

$('*').each(function() {
         if ($(this).is(':empty') == "") {
                   //Do Something
         }
});

Question #1: How would I convert .is(':empty') to vanilla js:

function isEmpty(el) {
  // Robust isEmpty logic here
}
document.querySelectorAll('*').filter(el => {
  return isEmpty(el);
});

Question #2: Will this logic traverse all children too?

Question #3: Could we make this querySelector for only blocklevel elements?

2

Answers


  1. The DOM version of “empty” is that there are no child nodes, which you can check in various ways, probably the two most common are:

    • firstChild will be null if there are no child nodes
    • The length of childNodes will be 0 if there are no child nodes

    So for instance, since null is falsy and filter works with truthy/falsy values:

    const allEmpty = [...document.querySelectorAll('*')].filter(el => !el.firstChild);
    

    (Note that querySelectorAll returns a NodeList and NodeList doesn’t have a filter method, so I used spread notation there to create an array from the NodeList [since NodeLists are iterable according to the spec now; but see this answer about ensuring that NodeList is iterable in your environment].)

    Question #2: Will this logic traverse all children too?

    querySelectorAll('*') selects every element in the document, many of which will be inside others also on the list.

    Question #3: Could we make this querySelector for only blocklevel elements?

    You can certainly tailor it according to the current specification and the default display browsers apply to them by doing a list of tag, such as querySelectorAll("div, p, h1"/*...*/). But that doesn’t tell you what’s a block element on that page, since CSS can change the display of elements. To get up-to-date information, you’d need to use getComputedStyle on each element after selecting it and check the display of the result.

    Building a list of all of the elements on a page may not be ideal, you might consider a recursive function instead:

    function findEmpty(element, results = []) {
        let child = element.firstChild;
        if (!child) {
            results.push(element);
        }
        while (child) {
            if (child.nodeType === Node.ELEMENT_NODE) {
                findEmpty(child, results);
            }
            child = child.nextSibling;
        }
        return results;
    }
    const allEmpty = findEmpty(document.body);
    

    But this partially comes down to what you consider “empty.” jQuery’s :empty selector is quite literal: An element is only empty if it doesn’t have any child nodes. But your definition may be more loose — for instance, perhaps you want to consider an element that contains only whitespace (directly or in descendant elements) “empty” for your purposes (in which case: !el.textContent.trim()). The first link above is to the DOM information on MDN which gives you lots to explore to determine your own “empty” if necessary.

    Login or Signup to reply.
  2. You can use children which is different from childNodes which can contain any node including text node. If using childNodes.length on div like <div>Some Text here</div> will be return a non empty array even though there is no child element or html tags

    function isEmpty(el) {
      return el.children.length
    }
    [...document.querySelectorAll('div.parent')].filter(function(el) {
      let k = isEmpty(el);
      console.log(k)
    });
    <div class='parent'>
      <div>Child 1</div>
      <div>Child 1</div>
      <div>Child 1</div>
      <div>Child 1</div>
    </div>
    
    <div class='parent'></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search