Explain Codes LogoExplain Codes Logo

How to check if element is visible after scrolling?

javascript
visibility-checks
intersection-observer
scroll-events
Alex KataevbyAlex Kataev·Sep 17, 2024
TLDR

For an instant visibility check after scrolling, use getBoundingClientRect(). It compares the element's bounds to the innerHeight and innerWidth of the viewport.

const isVisible = elem => { const { top, left, bottom, right } = elem.getBoundingClientRect(); // If these conditions meet, you've found Waldo! return top < window.innerHeight && left < window.innerWidth && bottom > 0 && right > 0; }; // Is elementId playing hide-and-seek? console.log(isVisible(document.getElementById('elementId'))); // true if visible

The function isVisible gives the green light (true) only if the element is in the viewport.

Understanding the basics of visibility checks

Expanding on the isVisible function, we'll dive deep into different scenarios and polish our approach for a range of use cases.

Full vs. Partial visibility

Your use case may dictate for full visibility where the entire element has to reside within the viewport. Alternatively, you might only need partial visibility where even a piece of the element within the viewport suffices.

Ensuring full visibility

For comprehensive visibility checks, meaning the whole element is sheathed within the viewport:

const isFullyVisible = elem => { const { top, left, bottom, right } = elem.getBoundingClientRect(); // SO says, "Element: you shall not pass... unless fully visible" return top >= 0 && left >= 0 && bottom <= window.innerHeight && right <= window.innerWidth; };

Allowing partial visibility

For the partial visibility check, ensure that any corner of the element is within the viewport:

const isPartlyVisible = elem => { const { top, left, bottom, right } = elem.getBoundingClientRect(); // Looking for a corner of truth in the sea of pixels return (top < window.innerHeight && top >= 0) || (bottom > 0 && bottom <= window.innerHeight) && (left < window.innerWidth && left >= 0) || (right > 0 && right <= window.innerWidth); };

Dealing with scrollable containers

Guess what, checking visibility within a scrollable container is tougher than finding a lost sock. Why? It's because you have to take into account the bounds of the container. Adventure up the DOM tree and adjust checks accordingly:

function isElementVisibleInContainer(elem, container) { let elemRect = elem.getBoundingClientRect(); let contRect = container.getBoundingClientRect(); return ( elemRect.top >= contRect.top && elemRect.left >= contRect.left && elemRect.bottom <= contRect.bottom && elemRect.right <= contRect.right ); }

Taming the IntersectionObserver API

Who needs to attach a scroll event listener when you can observe visibility changes like a hawk using the IntersectionObserver API?

const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // Gotcha! Element, you can't hide anymore. console.log(entry.target.id + ' is visible!'); } }); }, { root: null, threshold: 0.1 }); observer.observe(document.getElementById('elementId'));

Hold on, what's that threshold about? It sets the percentage of an element's visibility needed to trigger the callback, just like setting an alarm for a baking pie.

Dealing with edge cases

What's life without some complications? Learn to navigate different contexts for adequate visibility checks for real-world applications.

Dealing with dynamic content

For websites pumping out dynamic content with AJAX, attaching visibility checks to new elements is crucial. Events need to be bidirectional, binding to the document even after an AJAX call:

// AJAX: "Here are fresh elements!" // Document: "Fresh visibility checks attached!" $(document).on('DOMNodeInserted', function(e) { let newlyVisible = isVisible(e.target); if (newlyVisible) { // The new element can't hide anymore! } });

Adapting to viewport changes

When resizing the windows changes the viewport size or orientation, we reevaluate visibility checks by attaching listeners to the resize event, much like keeping up with latest fashion trends:

window.addEventListener('resize', () => { // Resize: "New viewport, who dis?" // Visibility check: "Let me get my tape measure..." });

Don't play favorites with browsers

Ensure your methods roll out the red carpet for all browsers, because the IntersectionObserver is like a VIP guest at a party and doesn't show up to all browsers, especially IE, to its own damn party.

Don't let performance hamper your style

Debounce scroll events to optimize performance and prevent visibility checks from overworking, because no one likes a show-off.