How to check if element is visible after scrolling in JavaScript?

Here’s how to check if an element is visible in the viewport after scrolling in JavaScript, including modern and legacy approaches:

1. Using getBoundingClientRect() (Legacy Method)

Checks if the element is at least partially visible in the viewport.

function isElementVisible(el) {
  const rect = el.getBoundingClientRect();
  const windowHeight = window.innerHeight || document.documentElement.clientHeight;
  const windowWidth = window.innerWidth || document.documentElement.clientWidth;

  // Check vertical and horizontal visibility
  const vertInView = rect.top <= windowHeight && rect.bottom >= 0;
  const horInView = rect.left <= windowWidth && rect.right >= 0;

  return vertInView && horInView;
}

Usage:

window.addEventListener('scroll', () => {
  const element = document.querySelector('#myElement');
  if (isElementVisible(element)) {
    console.log('Element is visible!');
  }
});

2. Check Full Visibility

Verify if the entire element is visible:

function isElementFullyVisible(el) {
  const rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

3. Intersection Observer API (Modern Method)

Efficiently detect visibility changes without constant scroll-event checks:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is visible!', entry.target);
    }
  });
}, {
  threshold: 0.1 // Trigger when 10% of the element is visible
});

// Start observing an element
const element = document.querySelector('#myElement');
observer.observe(element);

Options:

  • threshold: 0.0 (any visibility) to 1.0 (fully visible)
  • rootMargin: Adjust the detection boundary (e.g., “100px” to trigger earlier)

4. Combined Visibility Check

Ensure the element isn’t hidden by CSS and is in the viewport:

function isElementTrulyVisible(el) {
  // Check CSS visibility
  const style = window.getComputedStyle(el);
  if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
    return false;
  }

  // Check viewport position
  const rect = el.getBoundingClientRect();
  return (
    rect.top < window.innerHeight && rect.bottom > 0 &&
    rect.left < window.innerWidth && rect.right > 0
  );
}

5. Performance Optimization

Throttle Scroll Events

Avoid excessive checks during rapid scrolling:

function throttle(func, limit = 200) {
  let lastCall;
  return function() {
    const now = Date.now();
    if (!lastCall || (now - lastCall) >= limit) {
      func.apply(this, arguments);
      lastCall = now;
    }
  };
}

window.addEventListener('scroll', throttle(() => {
  if (isElementVisible(element)) {
    // Handle visibility
  }
}));

Key Scenarios

CasegetBoundingClientRectIntersectionObserver
Partial Visibility✅ (configurable)
Full Visibility
PerformanceNeeds throttlingBuilt-in optimization
Hidden via CSS❌ (requires extra checks)❌ (same)

When to Use Which

  • Legacy Approach: Support older browsers (IE9+) or need fine-grained control.
  • Intersection Observer: Modern apps, better performance, complex visibility rules.
  • Combined Checks: Critical UIs where hidden elements should never trigger visibility.

Example Workflow

  1. Use IntersectionObserver for scroll-driven content (e.g., lazy-load images).
  2. Use getBoundingClientRect for one-time checks or legacy support.
  3. Always pair with CSS visibility checks if elements might be hidden.

Leave a Reply

Your email address will not be published. Required fields are marked *