Check if Element is in Viewport
Detects if a DOM element is visible in the viewport (fully or partially)
Implementation
Copy the complete example or adapt its typed interface to your project. Comments are kept inside the snippet so the intent travels with the code.
/**
* Check if element is in viewport
* @param element Target element
* @param fullyInView Check if element is fully visible (default: false)
* @returns True if element is in viewport
*/
function isInViewport(element: HTMLElement, fullyInView = false): boolean {
const rect = element.getBoundingClientRect();
const viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
const viewWidth = Math.max(document.documentElement.clientWidth, window.innerWidth);
if (fullyInView) {
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= viewHeight &&
rect.right <= viewWidth
);
}
return (
rect.bottom > 0 &&
rect.right > 0 &&
rect.top < viewHeight &&
rect.left < viewWidth
);
}
// Usage Example
const element = document.getElementById('target');
window.addEventListener('scroll', () => {
if (isInViewport(element)) {
console.log('Element is visible!');
element.classList.add('visible');
} else {
element.classList.remove('visible');
}
});When to use it
Ideal for lazy loading images, triggering animations when elements enter viewport, or infinite scrolling
Review the input assumptions and browser or runtime support before adopting a utility unchanged. Small helpers are easiest to maintain when their contract stays narrow.
Reasoning and trade-offs
How it works
getBoundingClientRect reports the element edges relative to the viewport. Comparing those edges with viewport dimensions answers whether the entire box, or a chosen portion, is currently visible.
What to watch for
Transforms, clipping ancestors, sticky overlays, nested scrollers, and zero-sized elements complicate geometric checks. Running layout reads repeatedly during scroll can also force expensive rendering work.
Production use
Use IntersectionObserver for ongoing visibility tracking, lazy loading, or analytics. Reserve direct rectangle checks for one-off decisions, batch layout reads together, and define whether partial visibility actually satisfies the feature.
Before you ship
- Confirm the input contract with tests that cover empty, invalid, boundary, and representative real-world values before sharing the helper across a codebase.
- Keep the function focused on one responsibility and name any project-specific policy explicitly instead of hiding it inside a generic-looking utility.
- Measure behavior in the browsers or runtime versions your product supports, especially when timing, storage, DOM state, locale, or network access is involved.
- Document the return type, mutation behavior, failure mode, and performance characteristics beside the call site so future maintainers can evaluate changes safely.
- Compare the example with current official platform documentation when it relies on a browser API, language feature, or runtime behavior that may change over time.
- If the helper crosses a trust boundary, validate again on the server and avoid treating browser-side checks, TypeScript annotations, or formatted output as a security guarantee.
- Add a small regression test whenever a real edge case is discovered; concrete examples preserve the reason behind a guard more reliably than a general comment.