Event listener for when element becomes visible?

Viewed 208039

I am building a toolbar that is going to be included into a page. the div it is going to be included in will default to display:none. Is there a way i can put an event listener on my toolbar to listen for when it becomes visible so it can initialize? or will I have to pass it a variable from the containing page?

Thanks

11 Answers
var targetNode = document.getElementById('elementId');
var observer = new MutationObserver(function(){
    if(targetNode.style.display != 'none'){
        // doSomething
    }
});
observer.observe(targetNode, { attributes: true, childList: true });

I might be a little late, but you could just use the MutationObserver to observe any changes on the desired element. If any change occurs, you'll just have to check if the element is displayed.

If you just want to run some code when an element becomes visible in the viewport:

function onVisible(element, callback) {
  new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if(entry.intersectionRatio > 0) {
        callback(element);
        observer.disconnect();
      }
    });
  }).observe(element);
}

When the element has become visible (event slightly) the intersection observer calls callback and then destroys itself with .disconnect().

Use it like this:

onVisible(document.querySelector("#myElement"), () => console.log("it's visible"));

If you want the callback to trigger when the element becomes fully visible then you should change entry.intersectionRatio > 0 to entry.intersectionRatio === 1.

A simple solution to this which works even for nested elements is to use the ResizeObserver.

It should work in all modern browsers (https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API)

When an element has css rule display:none applied to it (whether directly or via an ancestor element) then all of its dimensions will be zero. So in order to detect becoming visible we just need an element with non-zero dimensions when visible.

const block=document.querySelector("#the-block")
const resizewatcher=new ResizeObserver(entries => {
  for (const entry of entries){
    console.log("Element",entry.target, 
      (entry.contentRect.width == 0) ? 
      "is now hidden" : 
      "is now visible"
    )
  }
})
resizewatcher.observe(block)

Expanding on Elmar's earlier answer, I used this to put focus on an input box in a Bootstrap navbar submenu.

I wanted the focus to go on the search box when the menu was expanded. .onfocus() wasn't working, I think because the element isn't visible at the time the event is triggered (even with the mouseup event). This worked perfectly though:

<ul class="navbar-nav ms-auto me-0 ps-3 ps-md-0">
    <li class="nav-item dropdown">
        <a class="nav-link dropdown-toggle" title="Search" id="navbardrop" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
            <i class="fas fa-search"></i>
        </a>
        <div class="dropdown-menu dropdown-menu-end search-menu">
            <form action="{% url 'search' %}" method="get">
                <div class="form-group row g-1 my-1 pb-1">
                    <div class="col">
                        <input type="text" name="query" id="searchbox" class="form-control py-1 ps-2" value="{% if search_query %}{{ search_query }}{% endif %}">
                    </div>
                    <div class="col-auto">
                        <input type="submit" value="Search" class="btn-primary form-control py-1">
                    </div>
                </div>
            </form>
        </div>
    </li>
</ul>

Then in the js:

respondToVisibility = function (element, callback) {
  var options = {
    root: document.documentElement,
  };

  var observer = new IntersectionObserver((entries, observer) => {
    entries.forEach((entry) => {
      callback(entry.intersectionRatio > 0);
    });
  }, options);

  observer.observe(element);
};

respondToVisibility(document.getElementById("searchbox"), (visible) => {
  if (visible) {
    document.getElementById("searchbox").focus();
  }
});
Related