javascriptcssweb-componentcustom-elementnative-web-component

Performant way to find out if an element or any of its ancestor elements has display: none


I need to find a very performant way to find out if a custom element or any of its parent elements has display: none;

First approach:

checkVisible() {
  let parentNodes = [];
  let el = this;
  while (!!(el = el.parentNode)) {
    parentNodes.push(el);
  }
  return [this, ...parentNodes].some(el => getComputedStyle(el).display === 'none') 
}

Is there anything that runs faster than this? Is this even a safe method?

The reason I need this: We have a <data-table> custom element (native webcomponent) which does very heavy lifting in its connectedCallback(). We have an application that has like 20-30 of those custom elements in a single page, which leads to IE 11 taking like 15 seconds until the page is rendered.

I need to delay initialisation of those <data-table> components which are initially not even visible, so I need a way to test inside the connectedCallback() if the element is visible (which it is not if it is in one of the 18 tabs initially not shown).


Solution

  • The easiest way to see if an element or its parent has display:none is to use el.offsetParent.

    const p1 = document.getElementById('parent1');
    const p2 = document.getElementById('parent2');
    const c1 = document.getElementById('child1');
    const c2 = document.getElementById('child2');
    const btn = document.getElementById('btn');
    const output = document.getElementById('output');
    
    function renderVisibility() {
      const p1state = isElementVisible(p1) ? 'is visible' : 'is not visible';
      const p2state = isElementVisible(p2) ? 'is visible' : 'is not visible';
      const c1state = isElementVisible(c1) ? 'is visible' : 'is not visible';
      const c2state = isElementVisible(c2) ? 'is visible' : 'is not visible';
      
      output.innerHTML = `Parent 1 ${p1state}<br>Parent 2 ${p2state}<br/>Child 1 ${c1state}<br/>Child 2 ${c2state}`;
    }
    
    function isElementVisible(el) {
      return !!el.offsetParent;
    }
    
    function toggle() {
      p1.style.display = (p1.style.display ? '' : 'none');
      p2.style.display = (p2.style.display ? '' : 'none');
      renderVisibility();
    }
    
    btn.addEventListener('click', toggle),
    renderVisibility();
    <div id="parent1" style="display:none">
      <div id="child1">child 1</div>
    </div>
    <div id="parent2">
      <div id="child2">second child</div>
    </div>
    <button id="btn">Toggle</button>
    <hr>
    <div id="output"></div>

    This code converts el.offsetParent into a boolean that indicates if the element is showing or not.

    This only works for display:none