Get height of non-overflowed portion of div - javascript

Say I have a wrapper div with a overflow:hidden on it and a div inside that that spans far below the visible portion. How can I get the visible height of the internal div?
<div id="wrapper" style="overflow: hidden; height:400px;">
<div id="inner">
<!--Lots of content in here-->
</div>
<div>
Every method I try attempting to get the height of the inner div returns the complete height including the hidden parts, i.e. 2000px. I want to be able to get the height of only the visible portion, so 400px in this example case.
I know I could just get the height of the parentNode, but in production, the inner div might not be a first child. So there might be other divs separating them, and so the height of #inner would be 400 - whatever the offsets of the elements between it and #wrapper.

As basic algorithm this could work:
var offset = 0;
var node = document.getElementById("inner");
while (node.offsetParent && node.offsetParent.id != "wrapper")
{
offset += node.offsetTop;
node = node.offsetParent;
}
var visible = node.offsetHeight - offset;
But if you're doing these kinds of things, maybe you already use jQuery, which might be of service with its .height() and .offset() functions:
$("#wrapper").height()-
$("#inner").offset()['top']+
$("#wrapper").offset()['top'];

Quick algorithm that goes up the DOM tree looking at window.getComputedStyle for overflow: hidden
function visibleArea(node){
var o = {height: node.offsetHeight, width: node.offsetWidth}, // size
d = {y: (node.offsetTop || 0), x: (node.offsetLeft || 0), node: node.offsetParent}, // position
css, y, x;
while( null !== (node = node.parentNode) ){ // loop up through DOM
css = window.getComputedStyle(node);
if( css && css.overflow === 'hidden' ){ // if has style && overflow
y = node.offsetHeight - d.y; // calculate visible y
x = node.offsetWidth - d.x; // and x
if( node !== d.node ){
y = y + (node.offsetTop || 0); // using || 0 in case it doesn't have an offsetParent
x = x + (node.offsetLeft || 0);
}
if( y < o.height ) {
if( y < 0 ) o.height = 0;
else o.height = y;
}
if( x < o.width ) {
if( x < 0 ) o.width = 0;
else o.width = x;
}
return o; // return (modify if you want to loop up again)
}
if( node === d.node ){ // update offsets
d.y = d.y + (node.offsetTop || 0);
d.x = d.x + (node.offsetLeft || 0);
d.node = node.offsetParent;
}
}
return o; // return if no hidden
}
example fiddle (look at your console).

The only way I've found to do this in every circumstance, including when there's overflow, transform: translate()s are used, and there are other nested containers in between an element and the element that's hiding its overflow is to combine .getBoundingClientRect() with a reference to the ancestor that's hiding the element's overflow:
function getVisibleDimensions(node, referenceNode) {
referenceNode = referenceNode || node.parentNode;
var pos = node.getBoundingClientRect();
var referencePos = referenceNode.getBoundingClientRect();
return {
"width": Math.min(
node.clientWidth,
referencePos.left + referenceNode.clientWidth - pos.left,
node.clientWidth - (referencePos.left - pos.left)
),
"height": Math.min(
node.clientHeight,
referencePos.top + referenceNode.clientHeight - pos.top,
node.clientHeight - (referencePos.top - pos.top)
)
}
}
Demo.
If a reference node is not given, the parent node is assumed: Demo.
Note that this doesn't take into account whether or not an element is viewable in the viewport, just visible (not hidden due to overflow). If you need both, you can combine functionality with this answer. It also has no check of visibility: hidden, so if you need that you need to check the style.visibility property of the node and all its ancestors.

I think keeping a sibling next to it, calculating its scrollTop and the overflow element scrollTop and then subtracting it from the siblings scroolTop might work

The code below computes the visible portion of an element. By visible portion I mean the part that is visible in the window, but I think you can easily alter it to base the computation on an arbitrary container element.
function computeVisibleHeight ($t) {
var top = $t.position().top;
var windowHeight = $(window).height();
var scrollTop = $(window).scrollTop();
var height = $t.height();
if (top < scrollTop && height - scrollTop >= windowHeight) {
// first case: the top and the bottom of the element is outside of the window
return windowHeight;
} else if (top < scrollTop) {
// second: the top is outside of the viewport but the bottom is visible
return height - (scrollTop - top);
} else if (top > scrollTop && top + height < windowHeight) {
// the whole element is visible
return height;
} else {
// the top is visible but the bottom is outside of the viewport
return windowHeight - (top - scrollTop);
}
}
The code is using jquery.

Related

Dynamic bottom value based on element position in browser

I am attempting to adapt this JS solution to keep a floating element above the footer of my site.
The adaption I am attempting is instead of changing the element position to absolute, I would have a dynamic bottom px value based on the position of the top of the footer, relevant to the client window.
function checkOffset() {
var onlineFloat = document.querySelector('#online-ceo');
var footer = document.querySelector('.site-footer');
function getRectTop(el){
var rect = el.getBoundingClientRect();
return rect.top;
}
if((getRectTop(onlineFloat) + document.body.scrollTop) + onlineFloat.offsetHeight >= (getRectTop(footer) + document.body.scrollTop) - 20)
var newBottom = ((getRectTop(footer) + document.body.scrollTop) - 40).toString().concat('px');
onlineFloat.style.bottom = newBottom;
if(document.body.scrollTop + window.innerHeight < (getRectTop(footer) + document.body.scrollTop))
onlineFloat.style.bottom = '20px';// restore when you scroll up
}
document.addEventListener("scroll", function(){
checkOffset();
});
The output of newBottom is currently a px value which changes on scroll, however, I am having issues setting this position to the element.
Where am I going wrong? Thanks.
With your approach (changing the bottom property), you can just calculate where the "float" should be if the footer's top position is in view (as in window.innerHeight) on scroll.
function checkOffset() {
var onlineFloat = document.querySelector('#online-ceo');
var footer = document.querySelector('.site-footer');
function getRectTop(el) {
var rect = el.getBoundingClientRect();
return rect.top;
}
var newBottom = 10 + (getRectTop(footer) < window.innerHeight ? window.innerHeight - getRectTop(footer) : 0) + 'px';
onlineFloat.style.bottom = newBottom;
}
document.addEventListener("scroll", function () {
checkOffset();
});

How to get all elements on the viewport using jQuery? [duplicate]

Is there an efficient way to tell if a DOM element (in an HTML document) is currently visible (appears in the viewport)?
(The question refers to Firefox.)
Now most browsers support getBoundingClientRect method, which has become the best practice. Using an old answer is very slow, not accurate and has several bugs.
The solution selected as correct is almost never precise.
This solution was tested on Internet Explorer 7 (and later), iOS 5 (and later) Safari, Android 2.0 (Eclair) and later, BlackBerry, Opera Mobile, and Internet Explorer Mobile 9.
function isElementInViewport (el) {
// Special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
);
}
How to use:
You can be sure that the function given above returns correct answer at the moment of time when it is called, but what about tracking element's visibility as an event?
Place the following code at the bottom of your <body> tag:
function onVisibilityChange(el, callback) {
var old_visible;
return function () {
var visible = isElementInViewport(el);
if (visible != old_visible) {
old_visible = visible;
if (typeof callback == 'function') {
callback();
}
}
}
}
var handler = onVisibilityChange(el, function() {
/* Your code go here */
});
// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);
/* // Non-jQuery
if (window.addEventListener) {
addEventListener('DOMContentLoaded', handler, false);
addEventListener('load', handler, false);
addEventListener('scroll', handler, false);
addEventListener('resize', handler, false);
} else if (window.attachEvent) {
attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
attachEvent('onload', handler);
attachEvent('onscroll', handler);
attachEvent('onresize', handler);
}
*/
If you do any DOM modifications, they can change your element's visibility of course.
Guidelines and common pitfalls:
Maybe you need to track page zoom / mobile device pinch? jQuery should handle zoom/pinch cross browser, otherwise first or second link should help you.
If you modify DOM, it can affect the element's visibility. You should take control over that and call handler() manually. Unfortunately, we don't have any cross browser onrepaint event. On the other hand that allows us to make optimizations and perform re-check only on DOM modifications that can change an element's visibility.
Never Ever use it inside jQuery $(document).ready() only, because there is no warranty CSS has been applied in this moment. Your code can work locally with your CSS on a hard drive, but once put on a remote server it will fail.
After DOMContentLoaded is fired, styles are applied, but the images are not loaded yet. So, we should add window.onload event listener.
We can't catch zoom/pinch event yet.
The last resort could be the following code:
/* TODO: this looks like a very bad code */
setInterval(handler, 600);
You can use the awesome feature pageVisibiliy of the HTML5 API if you care if the tab with your web page is active and visible.
TODO: this method does not handle two situations:
Overlapping using z-index.
Using overflow-scroll in element's container.
Try something new - The Intersection Observer API explained.
Update: Time marches on and so have our browsers. This technique is no longer recommended and you should use Dan's solution if you do not need to support version of Internet Explorer before 7.
Original solution (now outdated):
This will check if the element is entirely visible in the current viewport:
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
You could modify this simply to determine if any part of the element is visible in the viewport:
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
Update
In modern browsers, you might want to check out the Intersection Observer API which provides the following benefits:
Better performance than listening for scroll events
Works in cross domain iframes
Can tell if an element is obstructing/intersecting another
Intersection Observer is on its way to being a full-fledged standard and is already supported in Chrome 51+, Edge 15+ and Firefox 55+ and is under development for Safari. There's also a polyfill available.
Previous answer
There are some issues with the answer provided by Dan that might make it an unsuitable approach for some situations. Some of these issues are pointed out in his answer near the bottom, that his code will give false positives for elements that are:
Hidden by another element in front of the one being tested
Outside the visible area of a parent or ancestor element
An element or its children hidden by using the CSS clip property
These limitations are demonstrated in the following results of a simple test:
The solution: isElementVisible()
Here's a solution to those problems, with the test result below and an explanation of some parts of the code.
function isElementVisible(el) {
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || document.documentElement.clientWidth,
vHeight = window.innerHeight || document.documentElement.clientHeight,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight)
return false;
// Return true if any of its four corners are visible
return (
el.contains(efp(rect.left, rect.top))
|| el.contains(efp(rect.right, rect.top))
|| el.contains(efp(rect.right, rect.bottom))
|| el.contains(efp(rect.left, rect.bottom))
);
}
Passing test: http://jsfiddle.net/AndyE/cAY8c/
And the result:
Additional notes
This method is not without its own limitations, however. For instance, an element being tested with a lower z-index than another element at the same location would be identified as hidden even if the element in front doesn't actually hide any part of it. Still, this method has its uses in some cases that Dan's solution doesn't cover.
Both element.getBoundingClientRect() and document.elementFromPoint() are part of the CSSOM Working Draft specification and are supported in at least IE 6 and later and most desktop browsers for a long time (albeit, not perfectly). See Quirksmode on these functions for more information.
contains() is used to see if the element returned by document.elementFromPoint() is a child node of the element we're testing for visibility. It also returns true if the element returned is the same element. This just makes the check more robust. It's supported in all major browsers, Firefox 9.0 being the last of them to add it. For older Firefox support, check this answer's history.
If you want to test more points around the element for visibility―ie, to make sure the element isn't covered by more than, say, 50%―it wouldn't take much to adjust the last part of the answer. However, be aware that it would probably be very slow if you checked every pixel to make sure it was 100% visible.
I tried Dan's answer, however, the algebra used to determine the bounds means that the element must be both ≤ the viewport size and completely inside the viewport to get true, easily leading to false negatives. If you want to determine whether an element is in the viewport at all, ryanve's answer is close but the element being tested should overlap the viewport, so try this:
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return rect.bottom > 0 &&
rect.right > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}
We have now a native javascript Intersection Observer API
from which we can detect elements either they are in the viewport or not.
Here is example
const el = document.querySelector('#el')
const observer = new window.IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
console.log('ENTER')
return
}
console.log('LEAVE')
}, {
root: null,
threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
})
observer.observe(el);
body {
height: 300vh;
}
#el {
margin-top: 100vh;
}
<div id="el">this is element</div>
See the source of verge, which uses getBoundingClientRect. It's like:
function inViewport (element) {
if (!element) return false;
if (1 !== element.nodeType) return false;
var html = document.documentElement;
var rect = element.getBoundingClientRect();
return !!rect &&
rect.bottom >= 0 &&
rect.right >= 0 &&
rect.left <= html.clientWidth &&
rect.top <= html.clientHeight;
}
It returns true if any part of the element is in the viewport.
As a public service:
Dan's answer with the correct calculations (element can be > window, especially on mobile phone screens), and correct jQuery testing, as well as adding isElementPartiallyInViewport:
By the way, the difference between window.innerWidth and document.documentElement.clientWidth is that clientWidth/clientHeight doesn't include the scrollbar, while window.innerWidth/Height does.
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
Test-case
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Test</title>
<!--
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="scrollMonitor.js"></script>
-->
<script type="text/javascript">
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
// var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
// var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
</script>
</head>
<body>
<div style="display: block; width: 2000px; height: 10000px; background-color: green;">
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
<div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
t
</div>
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
</div>
<!--
<script type="text/javascript">
var element = document.getElementById("myele");
var watcher = scrollMonitor.create(element);
watcher.lock();
watcher.stateChange(function() {
console.log("state changed");
// $(element).toggleClass('fixed', this.isAboveViewport)
});
</script>
-->
</body>
</html>
My shorter and faster version:
function isElementOutViewport(el){
var rect = el.getBoundingClientRect();
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}
And a jsFiddle as required: https://jsfiddle.net/on1g619L/1/
The new Intersection Observer API addresses this question very directly.
This solution will need a polyfill as Safari, Opera and Internet Explorer don't support this yet (the polyfill is included in the solution).
In this solution, there is a box out of view that is the target (observed). When it comes into view, the button at the top in the header is hidden. It is shown once the box leaves the view.
const buttonToHide = document.querySelector('button');
const hideWhenBoxInView = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) { // If not in view
buttonToHide.style.display = "inherit";
} else {
buttonToHide.style.display = "none";
}
});
hideWhenBoxInView.observe(document.getElementById('box'));
header {
position: fixed;
top: 0;
width: 100vw;
height: 30px;
background-color: lightgreen;
}
.wrapper {
position: relative;
margin-top: 600px;
}
#box {
position: relative;
left: 175px;
width: 150px;
height: 135px;
background-color: lightblue;
border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
<button>NAVIGATION BUTTON TO HIDE</button>
</header>
<div class="wrapper">
<div id="box">
</div>
</div>
I found it troubling that there wasn't a jQuery-centric version of the functionality available. When I came across Dan's solution I spied the opportunity to provide something for folks who like to program in the jQuery OO style. It's nice and snappy and works like a charm for me.
Bada bing bada boom
$.fn.inView = function(){
if(!this.length)
return false;
var rect = this.get(0).getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
var all = [];
this.forEach(function(){
all.push( $(this).inView() );
});
return all.indexOf(false) === -1;
};
// Only the class elements in view
$('.some-class').filter(function(){
return $(this).inView();
});
// Only the class elements not in view
$('.some-class').filter(function(){
return !$(this).inView();
});
Usage
$(window).on('scroll',function(){
if( $('footer').inView() ) {
// Do cool stuff
}
});
The simplest solution as the support of Element.getBoundingClientRect() has become perfect:
function isInView(el) {
const box = el.getBoundingClientRect();
return box.top < window.innerHeight && box.bottom >= 0;
}
All answers I've encountered here only check if the element is positioned inside the current viewport. But that doesn't mean that it is visible.
What if the given element is inside a div with overflowing content, and it is scrolled out of view?
To solve that, you'd have to check if the element is contained by all parents.
My solution does exactly that:
It also allows you to specify how much of the element has to be visible.
Element.prototype.isVisible = function(percentX, percentY){
var tolerance = 0.01; //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
if(percentX == null){
percentX = 100;
}
if(percentY == null){
percentY = 100;
}
var elementRect = this.getBoundingClientRect();
var parentRects = [];
var element = this;
while(element.parentElement != null){
parentRects.push(element.parentElement.getBoundingClientRect());
element = element.parentElement;
}
var visibleInAllParents = parentRects.every(function(parentRect){
var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
var visiblePercentageX = visiblePixelX / elementRect.width * 100;
var visiblePercentageY = visiblePixelY / elementRect.height * 100;
return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
});
return visibleInAllParents;
};
This solution ignored the fact that elements may not be visible due to other facts, like opacity: 0.
I have tested this solution in Chrome and Internet Explorer 11.
I find that the accepted answer here is overly complicated for most use cases. This code does the job well (using jQuery) and differentiates between fully visible and partially visible elements:
var element = $("#element");
var topOfElement = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window = $(window);
$window.bind('scroll', function() {
var scrollTopPosition = $window.scrollTop()+$window.height();
var windowScrollTop = $window.scrollTop()
if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
// Element is partially visible (above viewable area)
console.log("Element is partially visible (above viewable area)");
} else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
// Element is hidden (above viewable area)
console.log("Element is hidden (above viewable area)");
} else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
// Element is hidden (below viewable area)
console.log("Element is hidden (below viewable area)");
} else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
// Element is partially visible (below viewable area)
console.log("Element is partially visible (below viewable area)");
} else {
// Element is completely visible
console.log("Element is completely visible");
}
});
Here's my solution. It will work if an element is hidden inside a scrollable container.
Here's a demo (try re-sizing the window to)
var visibleY = function(el){
var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
do {
rect = el.getBoundingClientRect();
if (top <= rect.bottom === false)
return false;
el = el.parentNode;
} while (el != document.body);
// Check it's within the document viewport
return top <= document.documentElement.clientHeight;
};
I only needed to check if it's visible in the Y axis (for a scrolling Ajax load-more-records feature).
I think this is a more functional way to do it.
Dan's answer do not work in a recursive context.
This function solves the problem when your element is inside others scrollable divs by testing any levels recursively up to the HTML tag, and stops at the first false.
/**
* fullVisible=true only returns true if the all object rect is visible
*/
function isReallyVisible(el, fullVisible) {
if ( el.tagName == "HTML" )
return true;
var parentRect=el.parentNode.getBoundingClientRect();
var rect = arguments[2] || el.getBoundingClientRect();
return (
( fullVisible ? rect.top >= parentRect.top : rect.bottom > parentRect.top ) &&
( fullVisible ? rect.left >= parentRect.left : rect.right > parentRect.left ) &&
( fullVisible ? rect.bottom <= parentRect.bottom : rect.top < parentRect.bottom ) &&
( fullVisible ? rect.right <= parentRect.right : rect.left < parentRect.right ) &&
isReallyVisible(el.parentNode, fullVisible, rect)
);
};
The most accepted answers don't work when zooming in Google Chrome on Android. In combination with Dan's answer, to account for Chrome on Android, visualViewport must be used. The following example only takes the vertical check into account and uses jQuery for the window height:
var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
ElTop -= window.visualViewport.offsetTop;
ElBottom -= window.visualViewport.offsetTop;
WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);
/**
* Returns Element placement information in Viewport
* #link https://stackoverflow.com/a/70476497/2453148
*
* #typedef {object} ViewportInfo - Whether the element is…
* #property {boolean} isInViewport - fully or partially in the viewport
* #property {boolean} isPartiallyInViewport - partially in the viewport
* #property {boolean} isInsideViewport - fully inside viewport
* #property {boolean} isAroundViewport - completely covers the viewport
* #property {boolean} isOnEdge - intersects the edge of viewport
* #property {boolean} isOnTopEdge - intersects the top edge
* #property {boolean} isOnRightEdge - intersects the right edge
* #property {boolean} isOnBottomEdge - is intersects the bottom edge
* #property {boolean} isOnLeftEdge - is intersects the left edge
*
* #param el Element
* #return {Object} ViewportInfo
*/
function getElementViewportInfo(el) {
let result = {};
let rect = el.getBoundingClientRect();
let windowHeight = window.innerHeight || document.documentElement.clientHeight;
let windowWidth = window.innerWidth || document.documentElement.clientWidth;
let insideX = rect.left >= 0 && rect.left + rect.width <= windowWidth;
let insideY = rect.top >= 0 && rect.top + rect.height <= windowHeight;
result.isInsideViewport = insideX && insideY;
let aroundX = rect.left < 0 && rect.left + rect.width > windowWidth;
let aroundY = rect.top < 0 && rect.top + rect.height > windowHeight;
result.isAroundViewport = aroundX && aroundY;
let onTop = rect.top < 0 && rect.top + rect.height > 0;
let onRight = rect.left < windowWidth && rect.left + rect.width > windowWidth;
let onLeft = rect.left < 0 && rect.left + rect.width > 0;
let onBottom = rect.top < windowHeight && rect.top + rect.height > windowHeight;
let onY = insideY || aroundY || onTop || onBottom;
let onX = insideX || aroundX || onLeft || onRight;
result.isOnTopEdge = onTop && onX;
result.isOnRightEdge = onRight && onY;
result.isOnBottomEdge = onBottom && onX;
result.isOnLeftEdge = onLeft && onY;
result.isOnEdge = result.isOnLeftEdge || result.isOnRightEdge ||
result.isOnTopEdge || result.isOnBottomEdge;
let isInX =
insideX || aroundX || result.isOnLeftEdge || result.isOnRightEdge;
let isInY =
insideY || aroundY || result.isOnTopEdge || result.isOnBottomEdge;
result.isInViewport = isInX && isInY;
result.isPartiallyInViewport =
result.isInViewport && result.isOnEdge;
return result;
}
Based on dan's solution, I had a go at cleaning up the implementation so that using it multiple times on the same page is easier:
$(function() {
$(window).on('load resize scroll', function() {
addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
addClassToElementInViewport($('.another-thing'), 'animate-thing');
// 👏 repeat as needed ...
});
function addClassToElementInViewport(element, newClass) {
if (inViewport(element)) {
element.addClass(newClass);
}
}
function inViewport(element) {
if (typeof jQuery === "function" && element instanceof jQuery) {
element = element[0];
}
var elementBounds = element.getBoundingClientRect();
return (
elementBounds.top >= 0 &&
elementBounds.left >= 0 &&
elementBounds.bottom <= $(window).height() &&
elementBounds.right <= $(window).width()
);
}
});
The way I'm using it is that when the element scrolls into view, I'm adding a class that triggers a CSS keyframe animation. It's pretty straightforward and works especially well when you've got like 10+ things to conditionally animate on a page.
Most of the usages in previous answers are failing at these points:
-When any pixel of an element is visible, but not "a corner",
-When an element is bigger than viewport and centered,
-Most of them are checking only for a singular element inside a document or window.
Well, for all these problems I've a solution and the plus sides are:
-You can return visible when only a pixel from any sides shows up and is not a corner,
-You can still return visible while element bigger than viewport,
-You can choose your parent element or you can automatically let it choose,
-Works on dynamically added elements too.
If you check the snippets below you will see the difference in using overflow-scroll in element's container will not cause any trouble and see that unlike other answers here even if a pixel shows up from any side or when an element is bigger than viewport and we are seeing inner pixels of the element it still works.
Usage is simple:
// For checking element visibility from any sides
isVisible(element)
// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)
// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice
A demonstration without crossSearchAlgorithm which is usefull for elements bigger than viewport check element3 inner pixels to see:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent, so it will be element's parent
if (isVisible(elementList[i])) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
You see, when you are inside the element3 it fails to tell if it's visible or not, because we are only checking if the element is visible from sides or corners.
And this one includes crossSearchAlgorithm which allows you to still return visible when the element is bigger than the viewport:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent so it will be element's parent
// and it will do crossSearchAlgorithm
if (isVisible(elementList[i],null,true)) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
JSFiddle to play with: http://jsfiddle.net/BerkerYuceer/grk5az2c/
This code is made for more precise information if any part of the element is shown in the view or not. For performance options or only vertical slides, do not use this! This code is more effective in drawing cases.
As simple as it can get, IMO:
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
return Math.abs(coords.top) <= coords.height;
}
Here is a function that tells if an element is in visible in the current viewport of a parent element:
function inParentViewport(el, pa) {
if (typeof jQuery === "function"){
if (el instanceof jQuery)
el = el[0];
if (pa instanceof jQuery)
pa = pa[0];
}
var e = el.getBoundingClientRect();
var p = pa.getBoundingClientRect();
return (
e.bottom >= p.top &&
e.right >= p.left &&
e.top <= p.bottom &&
e.left <= p.right
);
}
A better solution:
function getViewportSize(w) {
var w = w || window;
if(w.innerWidth != null)
return {w:w.innerWidth, h:w.innerHeight};
var d = w.document;
if (document.compatMode == "CSS1Compat") {
return {
w: d.documentElement.clientWidth,
h: d.documentElement.clientHeight
};
}
return { w: d.body.clientWidth, h: d.body.clientWidth };
}
function isViewportVisible(e) {
var box = e.getBoundingClientRect();
var height = box.height || (box.bottom - box.top);
var width = box.width || (box.right - box.left);
var viewport = getViewportSize();
if(!height || !width)
return false;
if(box.top > viewport.h || box.bottom < 0)
return false;
if(box.right < 0 || box.left > viewport.w)
return false;
return true;
}
I had the same question and figured it out by using getBoundingClientRect().
This code is completely 'generic' and only has to be written once for it to work (you don't have to write it out for each element that you want to know is in the viewport).
This code only checks to see if it is vertically in the viewport, not horizontally. In this case, the variable (array) 'elements' holds all the elements that you are checking to be vertically in the viewport, so grab any elements you want anywhere and store them there.
The 'for loop', loops through each element and checks to see if it is vertically in the viewport. This code executes every time the user scrolls! If the getBoudingClientRect().top is less than 3/4 the viewport (the element is one quarter in the viewport), it registers as 'in the viewport'.
Since the code is generic, you will want to know 'which' element is in the viewport. To find that out, you can determine it by custom attribute, node name, id, class name, and more.
Here is my code (tell me if it doesn't work; it has been tested in Internet Explorer 11, Firefox 40.0.3, Chrome Version 45.0.2454.85 m, Opera 31.0.1889.174, and Edge with Windows 10, [not Safari yet])...
// Scrolling handlers...
window.onscroll = function(){
var elements = document.getElementById('whatever').getElementsByClassName('whatever');
for(var i = 0; i != elements.length; i++)
{
if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
elements[i].getBoundingClientRect().top > 0)
{
console.log(elements[i].nodeName + ' ' +
elements[i].className + ' ' +
elements[i].id +
' is in the viewport; proceed with whatever code you want to do here.');
}
};
This checks if an element is at least partially in view (vertical dimension):
function inView(element) {
var box = element.getBoundingClientRect();
return inViewBox(box);
}
function inViewBox(box) {
return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}
function getWindowSize() {
return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}
This is the easy and small solution that has worked for me.
Example: You want to see if the element is visible in the parent element that has overflow scroll.
$(window).on('scroll', function () {
var container = $('#sidebar');
var containerHeight = container.height();
var scrollPosition = $('#row1').offset().top - container.offset().top;
if (containerHeight < scrollPosition) {
console.log('not visible');
} else {
console.log('visible');
}
})
All the answers here are determining if the element is fully contained within the viewport, not just visible in some way. For example, if only half of an image is visible at the bottom of the view, the solutions here will fail, considering that "outside".
I had a use case where I'm doing lazy loading via IntersectionObserver, but due to animations that occur during pop-in, I didn't want to observe any images that were already intersected on page load. To do that, I used the following code:
const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
(0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));
This is basically checking to see if either the top or bottom bound is independently in the viewport. The opposite end may be outside, but as long as one end is in, it's "visible" at least partially.
I use this function (it only checks if the y is inscreen since most of the time the x is not needed)
function elementInViewport(el) {
var elinfo = {
"top":el.offsetTop,
"height":el.offsetHeight,
};
if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
return false;
} else {
return true;
}
}
Here is a snippet to check if the given element is fully visible in its parent:
export const visibleInParentViewport = (el) => {
const elementRect = el.getBoundingClientRect();
const parentRect = el.parentNode.getBoundingClientRect();
return (
elementRect.top >= parentRect.top &&
elementRect.right >= parentRect.left &&
elementRect.top + elementRect.height <= parentRect.bottom &&
elementRect.left + elementRect.width <= parentRect.right
);
}
const isHTMLElementInView = (element: HTMLElement) => {
const rect = element?.getBoundingClientRect()
if (!rect) return
return rect.top <= window.innerHeight && rect.bottom >= 0
}
This function checks if the element is in the viewport on vertical level.
Domysee's answer https://stackoverflow.com/a/37998526 is close to correct.
Many examples use "completely contained in the viewport" and his code uses percentages to allow for partially visible. His code also addresses the "is a parent clipping the view" question, which most examples ignore.
One missing element is the impact of the parent's scrollbars - getBoundingClientRect returns the outer rectangle of the parent, which includes the scroll bars, not the inner rectangle, which doesn't. A child can hide behind the parent scroll bar and be considered visible when it isn't.
The recommended observer pattern isn't appropriate for my use case: using the arrow keys to change the currently selected row in a table, and make sure the new selection is visible. Using an observer for this would be excessively convoluted.
Here's some code -
it includes an additional hack (fudgeY) because my table has a sticky header that isn't detectable by straightforward means (and handling this automatically would be pretty tedious). Also, it uses decimal (0 to 1) instead of percentage for the required visible fraction. (For my case I need full y, and x isn't relevant).
function intersectRect(r1, r2) {
var r = {};
r.left = r1.left < r2.left ? r2.left : r1.left;
r.top = r1.top < r2.top ? r2.top : r1.top;
r.right = r1.right < r2.right ? r1.right : r2.right;
r.bottom = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
if (r.left < r.right && r.top < r.bottom)
return r;
return null;
}
function innerRect(e) {
var b,r;
b = e.getBoundingClientRect();
r = {};
r.left = b.left;
r.top = b.top;
r.right = b.left + e.clientWidth;
r.bottom = b.top + e.clientHeight;
return r;
}
function isViewable(e, fracX, fracY, fudgeY) {
// ref https://stackoverflow.com/a/37998526
// intersect all the rects and then check the result once
// innerRect: mind the scroll bars
// fudgeY: handle "sticky" thead in parent table. Ugh.
var r, pr, er;
er = e.getBoundingClientRect();
r = er;
for (;;) {
e = e.parentElement;
if (!e)
break;
pr = innerRect(e);
if (fudgeY)
pr.top += fudgeY;
r = intersectRect(r, pr);
if (!r)
return false;
}
if (fracX && ((r.right-r.left) / (er.right-er.left)) < (fracX-0.001))
return false;
if (fracY && ((r.bottom-r.top) / (er.bottom-er.top)) < (fracY-0.001))
return false;
return true;
}

Absolute positioned element which does not escape viewport

I have a tooltip element which is displayed when hovering over certain elements.
I would like the tooltip to be positioned above the normal element as expected but in the case where the tooltip is too large and escapes the window, I need this to NOT happen.
How can I have an element which is absolutely positioned and also never displays out of view?
Edit: Preferably using CSS...
Following the idea presented here: How to tell if a DOM element is visible in the current viewport?
I'd got with:
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
var tooltip = $(".tooltip");
tooltip.show(); //we must make it visible in order to perform the test on the next line
if(!elementInViewport(tooltip)) {
tooltip.hide();
}

Some higher level abstractions for detecting DOM element visibility [duplicate]

Is there an efficient way to tell if a DOM element (in an HTML document) is currently visible (appears in the viewport)?
(The question refers to Firefox.)
Now most browsers support getBoundingClientRect method, which has become the best practice. Using an old answer is very slow, not accurate and has several bugs.
The solution selected as correct is almost never precise.
This solution was tested on Internet Explorer 7 (and later), iOS 5 (and later) Safari, Android 2.0 (Eclair) and later, BlackBerry, Opera Mobile, and Internet Explorer Mobile 9.
function isElementInViewport (el) {
// Special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
);
}
How to use:
You can be sure that the function given above returns correct answer at the moment of time when it is called, but what about tracking element's visibility as an event?
Place the following code at the bottom of your <body> tag:
function onVisibilityChange(el, callback) {
var old_visible;
return function () {
var visible = isElementInViewport(el);
if (visible != old_visible) {
old_visible = visible;
if (typeof callback == 'function') {
callback();
}
}
}
}
var handler = onVisibilityChange(el, function() {
/* Your code go here */
});
// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);
/* // Non-jQuery
if (window.addEventListener) {
addEventListener('DOMContentLoaded', handler, false);
addEventListener('load', handler, false);
addEventListener('scroll', handler, false);
addEventListener('resize', handler, false);
} else if (window.attachEvent) {
attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
attachEvent('onload', handler);
attachEvent('onscroll', handler);
attachEvent('onresize', handler);
}
*/
If you do any DOM modifications, they can change your element's visibility of course.
Guidelines and common pitfalls:
Maybe you need to track page zoom / mobile device pinch? jQuery should handle zoom/pinch cross browser, otherwise first or second link should help you.
If you modify DOM, it can affect the element's visibility. You should take control over that and call handler() manually. Unfortunately, we don't have any cross browser onrepaint event. On the other hand that allows us to make optimizations and perform re-check only on DOM modifications that can change an element's visibility.
Never Ever use it inside jQuery $(document).ready() only, because there is no warranty CSS has been applied in this moment. Your code can work locally with your CSS on a hard drive, but once put on a remote server it will fail.
After DOMContentLoaded is fired, styles are applied, but the images are not loaded yet. So, we should add window.onload event listener.
We can't catch zoom/pinch event yet.
The last resort could be the following code:
/* TODO: this looks like a very bad code */
setInterval(handler, 600);
You can use the awesome feature pageVisibiliy of the HTML5 API if you care if the tab with your web page is active and visible.
TODO: this method does not handle two situations:
Overlapping using z-index.
Using overflow-scroll in element's container.
Try something new - The Intersection Observer API explained.
Update: Time marches on and so have our browsers. This technique is no longer recommended and you should use Dan's solution if you do not need to support version of Internet Explorer before 7.
Original solution (now outdated):
This will check if the element is entirely visible in the current viewport:
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
You could modify this simply to determine if any part of the element is visible in the viewport:
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
Update
In modern browsers, you might want to check out the Intersection Observer API which provides the following benefits:
Better performance than listening for scroll events
Works in cross domain iframes
Can tell if an element is obstructing/intersecting another
Intersection Observer is on its way to being a full-fledged standard and is already supported in Chrome 51+, Edge 15+ and Firefox 55+ and is under development for Safari. There's also a polyfill available.
Previous answer
There are some issues with the answer provided by Dan that might make it an unsuitable approach for some situations. Some of these issues are pointed out in his answer near the bottom, that his code will give false positives for elements that are:
Hidden by another element in front of the one being tested
Outside the visible area of a parent or ancestor element
An element or its children hidden by using the CSS clip property
These limitations are demonstrated in the following results of a simple test:
The solution: isElementVisible()
Here's a solution to those problems, with the test result below and an explanation of some parts of the code.
function isElementVisible(el) {
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || document.documentElement.clientWidth,
vHeight = window.innerHeight || document.documentElement.clientHeight,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight)
return false;
// Return true if any of its four corners are visible
return (
el.contains(efp(rect.left, rect.top))
|| el.contains(efp(rect.right, rect.top))
|| el.contains(efp(rect.right, rect.bottom))
|| el.contains(efp(rect.left, rect.bottom))
);
}
Passing test: http://jsfiddle.net/AndyE/cAY8c/
And the result:
Additional notes
This method is not without its own limitations, however. For instance, an element being tested with a lower z-index than another element at the same location would be identified as hidden even if the element in front doesn't actually hide any part of it. Still, this method has its uses in some cases that Dan's solution doesn't cover.
Both element.getBoundingClientRect() and document.elementFromPoint() are part of the CSSOM Working Draft specification and are supported in at least IE 6 and later and most desktop browsers for a long time (albeit, not perfectly). See Quirksmode on these functions for more information.
contains() is used to see if the element returned by document.elementFromPoint() is a child node of the element we're testing for visibility. It also returns true if the element returned is the same element. This just makes the check more robust. It's supported in all major browsers, Firefox 9.0 being the last of them to add it. For older Firefox support, check this answer's history.
If you want to test more points around the element for visibility―ie, to make sure the element isn't covered by more than, say, 50%―it wouldn't take much to adjust the last part of the answer. However, be aware that it would probably be very slow if you checked every pixel to make sure it was 100% visible.
I tried Dan's answer, however, the algebra used to determine the bounds means that the element must be both ≤ the viewport size and completely inside the viewport to get true, easily leading to false negatives. If you want to determine whether an element is in the viewport at all, ryanve's answer is close but the element being tested should overlap the viewport, so try this:
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return rect.bottom > 0 &&
rect.right > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}
We have now a native javascript Intersection Observer API
from which we can detect elements either they are in the viewport or not.
Here is example
const el = document.querySelector('#el')
const observer = new window.IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
console.log('ENTER')
return
}
console.log('LEAVE')
}, {
root: null,
threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
})
observer.observe(el);
body {
height: 300vh;
}
#el {
margin-top: 100vh;
}
<div id="el">this is element</div>
See the source of verge, which uses getBoundingClientRect. It's like:
function inViewport (element) {
if (!element) return false;
if (1 !== element.nodeType) return false;
var html = document.documentElement;
var rect = element.getBoundingClientRect();
return !!rect &&
rect.bottom >= 0 &&
rect.right >= 0 &&
rect.left <= html.clientWidth &&
rect.top <= html.clientHeight;
}
It returns true if any part of the element is in the viewport.
As a public service:
Dan's answer with the correct calculations (element can be > window, especially on mobile phone screens), and correct jQuery testing, as well as adding isElementPartiallyInViewport:
By the way, the difference between window.innerWidth and document.documentElement.clientWidth is that clientWidth/clientHeight doesn't include the scrollbar, while window.innerWidth/Height does.
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
Test-case
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Test</title>
<!--
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="scrollMonitor.js"></script>
-->
<script type="text/javascript">
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
// var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
// var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
</script>
</head>
<body>
<div style="display: block; width: 2000px; height: 10000px; background-color: green;">
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
<div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
t
</div>
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
</div>
<!--
<script type="text/javascript">
var element = document.getElementById("myele");
var watcher = scrollMonitor.create(element);
watcher.lock();
watcher.stateChange(function() {
console.log("state changed");
// $(element).toggleClass('fixed', this.isAboveViewport)
});
</script>
-->
</body>
</html>
My shorter and faster version:
function isElementOutViewport(el){
var rect = el.getBoundingClientRect();
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}
And a jsFiddle as required: https://jsfiddle.net/on1g619L/1/
The new Intersection Observer API addresses this question very directly.
This solution will need a polyfill as Safari, Opera and Internet Explorer don't support this yet (the polyfill is included in the solution).
In this solution, there is a box out of view that is the target (observed). When it comes into view, the button at the top in the header is hidden. It is shown once the box leaves the view.
const buttonToHide = document.querySelector('button');
const hideWhenBoxInView = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) { // If not in view
buttonToHide.style.display = "inherit";
} else {
buttonToHide.style.display = "none";
}
});
hideWhenBoxInView.observe(document.getElementById('box'));
header {
position: fixed;
top: 0;
width: 100vw;
height: 30px;
background-color: lightgreen;
}
.wrapper {
position: relative;
margin-top: 600px;
}
#box {
position: relative;
left: 175px;
width: 150px;
height: 135px;
background-color: lightblue;
border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
<button>NAVIGATION BUTTON TO HIDE</button>
</header>
<div class="wrapper">
<div id="box">
</div>
</div>
I found it troubling that there wasn't a jQuery-centric version of the functionality available. When I came across Dan's solution I spied the opportunity to provide something for folks who like to program in the jQuery OO style. It's nice and snappy and works like a charm for me.
Bada bing bada boom
$.fn.inView = function(){
if(!this.length)
return false;
var rect = this.get(0).getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
var all = [];
this.forEach(function(){
all.push( $(this).inView() );
});
return all.indexOf(false) === -1;
};
// Only the class elements in view
$('.some-class').filter(function(){
return $(this).inView();
});
// Only the class elements not in view
$('.some-class').filter(function(){
return !$(this).inView();
});
Usage
$(window).on('scroll',function(){
if( $('footer').inView() ) {
// Do cool stuff
}
});
The simplest solution as the support of Element.getBoundingClientRect() has become perfect:
function isInView(el) {
const box = el.getBoundingClientRect();
return box.top < window.innerHeight && box.bottom >= 0;
}
All answers I've encountered here only check if the element is positioned inside the current viewport. But that doesn't mean that it is visible.
What if the given element is inside a div with overflowing content, and it is scrolled out of view?
To solve that, you'd have to check if the element is contained by all parents.
My solution does exactly that:
It also allows you to specify how much of the element has to be visible.
Element.prototype.isVisible = function(percentX, percentY){
var tolerance = 0.01; //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
if(percentX == null){
percentX = 100;
}
if(percentY == null){
percentY = 100;
}
var elementRect = this.getBoundingClientRect();
var parentRects = [];
var element = this;
while(element.parentElement != null){
parentRects.push(element.parentElement.getBoundingClientRect());
element = element.parentElement;
}
var visibleInAllParents = parentRects.every(function(parentRect){
var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
var visiblePercentageX = visiblePixelX / elementRect.width * 100;
var visiblePercentageY = visiblePixelY / elementRect.height * 100;
return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
});
return visibleInAllParents;
};
This solution ignored the fact that elements may not be visible due to other facts, like opacity: 0.
I have tested this solution in Chrome and Internet Explorer 11.
I find that the accepted answer here is overly complicated for most use cases. This code does the job well (using jQuery) and differentiates between fully visible and partially visible elements:
var element = $("#element");
var topOfElement = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window = $(window);
$window.bind('scroll', function() {
var scrollTopPosition = $window.scrollTop()+$window.height();
var windowScrollTop = $window.scrollTop()
if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
// Element is partially visible (above viewable area)
console.log("Element is partially visible (above viewable area)");
} else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
// Element is hidden (above viewable area)
console.log("Element is hidden (above viewable area)");
} else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
// Element is hidden (below viewable area)
console.log("Element is hidden (below viewable area)");
} else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
// Element is partially visible (below viewable area)
console.log("Element is partially visible (below viewable area)");
} else {
// Element is completely visible
console.log("Element is completely visible");
}
});
Here's my solution. It will work if an element is hidden inside a scrollable container.
Here's a demo (try re-sizing the window to)
var visibleY = function(el){
var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
do {
rect = el.getBoundingClientRect();
if (top <= rect.bottom === false)
return false;
el = el.parentNode;
} while (el != document.body);
// Check it's within the document viewport
return top <= document.documentElement.clientHeight;
};
I only needed to check if it's visible in the Y axis (for a scrolling Ajax load-more-records feature).
I think this is a more functional way to do it.
Dan's answer do not work in a recursive context.
This function solves the problem when your element is inside others scrollable divs by testing any levels recursively up to the HTML tag, and stops at the first false.
/**
* fullVisible=true only returns true if the all object rect is visible
*/
function isReallyVisible(el, fullVisible) {
if ( el.tagName == "HTML" )
return true;
var parentRect=el.parentNode.getBoundingClientRect();
var rect = arguments[2] || el.getBoundingClientRect();
return (
( fullVisible ? rect.top >= parentRect.top : rect.bottom > parentRect.top ) &&
( fullVisible ? rect.left >= parentRect.left : rect.right > parentRect.left ) &&
( fullVisible ? rect.bottom <= parentRect.bottom : rect.top < parentRect.bottom ) &&
( fullVisible ? rect.right <= parentRect.right : rect.left < parentRect.right ) &&
isReallyVisible(el.parentNode, fullVisible, rect)
);
};
The most accepted answers don't work when zooming in Google Chrome on Android. In combination with Dan's answer, to account for Chrome on Android, visualViewport must be used. The following example only takes the vertical check into account and uses jQuery for the window height:
var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
ElTop -= window.visualViewport.offsetTop;
ElBottom -= window.visualViewport.offsetTop;
WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);
/**
* Returns Element placement information in Viewport
* #link https://stackoverflow.com/a/70476497/2453148
*
* #typedef {object} ViewportInfo - Whether the element is…
* #property {boolean} isInViewport - fully or partially in the viewport
* #property {boolean} isPartiallyInViewport - partially in the viewport
* #property {boolean} isInsideViewport - fully inside viewport
* #property {boolean} isAroundViewport - completely covers the viewport
* #property {boolean} isOnEdge - intersects the edge of viewport
* #property {boolean} isOnTopEdge - intersects the top edge
* #property {boolean} isOnRightEdge - intersects the right edge
* #property {boolean} isOnBottomEdge - is intersects the bottom edge
* #property {boolean} isOnLeftEdge - is intersects the left edge
*
* #param el Element
* #return {Object} ViewportInfo
*/
function getElementViewportInfo(el) {
let result = {};
let rect = el.getBoundingClientRect();
let windowHeight = window.innerHeight || document.documentElement.clientHeight;
let windowWidth = window.innerWidth || document.documentElement.clientWidth;
let insideX = rect.left >= 0 && rect.left + rect.width <= windowWidth;
let insideY = rect.top >= 0 && rect.top + rect.height <= windowHeight;
result.isInsideViewport = insideX && insideY;
let aroundX = rect.left < 0 && rect.left + rect.width > windowWidth;
let aroundY = rect.top < 0 && rect.top + rect.height > windowHeight;
result.isAroundViewport = aroundX && aroundY;
let onTop = rect.top < 0 && rect.top + rect.height > 0;
let onRight = rect.left < windowWidth && rect.left + rect.width > windowWidth;
let onLeft = rect.left < 0 && rect.left + rect.width > 0;
let onBottom = rect.top < windowHeight && rect.top + rect.height > windowHeight;
let onY = insideY || aroundY || onTop || onBottom;
let onX = insideX || aroundX || onLeft || onRight;
result.isOnTopEdge = onTop && onX;
result.isOnRightEdge = onRight && onY;
result.isOnBottomEdge = onBottom && onX;
result.isOnLeftEdge = onLeft && onY;
result.isOnEdge = result.isOnLeftEdge || result.isOnRightEdge ||
result.isOnTopEdge || result.isOnBottomEdge;
let isInX =
insideX || aroundX || result.isOnLeftEdge || result.isOnRightEdge;
let isInY =
insideY || aroundY || result.isOnTopEdge || result.isOnBottomEdge;
result.isInViewport = isInX && isInY;
result.isPartiallyInViewport =
result.isInViewport && result.isOnEdge;
return result;
}
Based on dan's solution, I had a go at cleaning up the implementation so that using it multiple times on the same page is easier:
$(function() {
$(window).on('load resize scroll', function() {
addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
addClassToElementInViewport($('.another-thing'), 'animate-thing');
// 👏 repeat as needed ...
});
function addClassToElementInViewport(element, newClass) {
if (inViewport(element)) {
element.addClass(newClass);
}
}
function inViewport(element) {
if (typeof jQuery === "function" && element instanceof jQuery) {
element = element[0];
}
var elementBounds = element.getBoundingClientRect();
return (
elementBounds.top >= 0 &&
elementBounds.left >= 0 &&
elementBounds.bottom <= $(window).height() &&
elementBounds.right <= $(window).width()
);
}
});
The way I'm using it is that when the element scrolls into view, I'm adding a class that triggers a CSS keyframe animation. It's pretty straightforward and works especially well when you've got like 10+ things to conditionally animate on a page.
Most of the usages in previous answers are failing at these points:
-When any pixel of an element is visible, but not "a corner",
-When an element is bigger than viewport and centered,
-Most of them are checking only for a singular element inside a document or window.
Well, for all these problems I've a solution and the plus sides are:
-You can return visible when only a pixel from any sides shows up and is not a corner,
-You can still return visible while element bigger than viewport,
-You can choose your parent element or you can automatically let it choose,
-Works on dynamically added elements too.
If you check the snippets below you will see the difference in using overflow-scroll in element's container will not cause any trouble and see that unlike other answers here even if a pixel shows up from any side or when an element is bigger than viewport and we are seeing inner pixels of the element it still works.
Usage is simple:
// For checking element visibility from any sides
isVisible(element)
// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)
// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice
A demonstration without crossSearchAlgorithm which is usefull for elements bigger than viewport check element3 inner pixels to see:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent, so it will be element's parent
if (isVisible(elementList[i])) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
You see, when you are inside the element3 it fails to tell if it's visible or not, because we are only checking if the element is visible from sides or corners.
And this one includes crossSearchAlgorithm which allows you to still return visible when the element is bigger than the viewport:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent so it will be element's parent
// and it will do crossSearchAlgorithm
if (isVisible(elementList[i],null,true)) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
JSFiddle to play with: http://jsfiddle.net/BerkerYuceer/grk5az2c/
This code is made for more precise information if any part of the element is shown in the view or not. For performance options or only vertical slides, do not use this! This code is more effective in drawing cases.
As simple as it can get, IMO:
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
return Math.abs(coords.top) <= coords.height;
}
Here is a function that tells if an element is in visible in the current viewport of a parent element:
function inParentViewport(el, pa) {
if (typeof jQuery === "function"){
if (el instanceof jQuery)
el = el[0];
if (pa instanceof jQuery)
pa = pa[0];
}
var e = el.getBoundingClientRect();
var p = pa.getBoundingClientRect();
return (
e.bottom >= p.top &&
e.right >= p.left &&
e.top <= p.bottom &&
e.left <= p.right
);
}
A better solution:
function getViewportSize(w) {
var w = w || window;
if(w.innerWidth != null)
return {w:w.innerWidth, h:w.innerHeight};
var d = w.document;
if (document.compatMode == "CSS1Compat") {
return {
w: d.documentElement.clientWidth,
h: d.documentElement.clientHeight
};
}
return { w: d.body.clientWidth, h: d.body.clientWidth };
}
function isViewportVisible(e) {
var box = e.getBoundingClientRect();
var height = box.height || (box.bottom - box.top);
var width = box.width || (box.right - box.left);
var viewport = getViewportSize();
if(!height || !width)
return false;
if(box.top > viewport.h || box.bottom < 0)
return false;
if(box.right < 0 || box.left > viewport.w)
return false;
return true;
}
I had the same question and figured it out by using getBoundingClientRect().
This code is completely 'generic' and only has to be written once for it to work (you don't have to write it out for each element that you want to know is in the viewport).
This code only checks to see if it is vertically in the viewport, not horizontally. In this case, the variable (array) 'elements' holds all the elements that you are checking to be vertically in the viewport, so grab any elements you want anywhere and store them there.
The 'for loop', loops through each element and checks to see if it is vertically in the viewport. This code executes every time the user scrolls! If the getBoudingClientRect().top is less than 3/4 the viewport (the element is one quarter in the viewport), it registers as 'in the viewport'.
Since the code is generic, you will want to know 'which' element is in the viewport. To find that out, you can determine it by custom attribute, node name, id, class name, and more.
Here is my code (tell me if it doesn't work; it has been tested in Internet Explorer 11, Firefox 40.0.3, Chrome Version 45.0.2454.85 m, Opera 31.0.1889.174, and Edge with Windows 10, [not Safari yet])...
// Scrolling handlers...
window.onscroll = function(){
var elements = document.getElementById('whatever').getElementsByClassName('whatever');
for(var i = 0; i != elements.length; i++)
{
if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
elements[i].getBoundingClientRect().top > 0)
{
console.log(elements[i].nodeName + ' ' +
elements[i].className + ' ' +
elements[i].id +
' is in the viewport; proceed with whatever code you want to do here.');
}
};
This checks if an element is at least partially in view (vertical dimension):
function inView(element) {
var box = element.getBoundingClientRect();
return inViewBox(box);
}
function inViewBox(box) {
return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}
function getWindowSize() {
return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}
This is the easy and small solution that has worked for me.
Example: You want to see if the element is visible in the parent element that has overflow scroll.
$(window).on('scroll', function () {
var container = $('#sidebar');
var containerHeight = container.height();
var scrollPosition = $('#row1').offset().top - container.offset().top;
if (containerHeight < scrollPosition) {
console.log('not visible');
} else {
console.log('visible');
}
})
All the answers here are determining if the element is fully contained within the viewport, not just visible in some way. For example, if only half of an image is visible at the bottom of the view, the solutions here will fail, considering that "outside".
I had a use case where I'm doing lazy loading via IntersectionObserver, but due to animations that occur during pop-in, I didn't want to observe any images that were already intersected on page load. To do that, I used the following code:
const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
(0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));
This is basically checking to see if either the top or bottom bound is independently in the viewport. The opposite end may be outside, but as long as one end is in, it's "visible" at least partially.
I use this function (it only checks if the y is inscreen since most of the time the x is not needed)
function elementInViewport(el) {
var elinfo = {
"top":el.offsetTop,
"height":el.offsetHeight,
};
if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
return false;
} else {
return true;
}
}
Here is a snippet to check if the given element is fully visible in its parent:
export const visibleInParentViewport = (el) => {
const elementRect = el.getBoundingClientRect();
const parentRect = el.parentNode.getBoundingClientRect();
return (
elementRect.top >= parentRect.top &&
elementRect.right >= parentRect.left &&
elementRect.top + elementRect.height <= parentRect.bottom &&
elementRect.left + elementRect.width <= parentRect.right
);
}
const isHTMLElementInView = (element: HTMLElement) => {
const rect = element?.getBoundingClientRect()
if (!rect) return
return rect.top <= window.innerHeight && rect.bottom >= 0
}
This function checks if the element is in the viewport on vertical level.
Domysee's answer https://stackoverflow.com/a/37998526 is close to correct.
Many examples use "completely contained in the viewport" and his code uses percentages to allow for partially visible. His code also addresses the "is a parent clipping the view" question, which most examples ignore.
One missing element is the impact of the parent's scrollbars - getBoundingClientRect returns the outer rectangle of the parent, which includes the scroll bars, not the inner rectangle, which doesn't. A child can hide behind the parent scroll bar and be considered visible when it isn't.
The recommended observer pattern isn't appropriate for my use case: using the arrow keys to change the currently selected row in a table, and make sure the new selection is visible. Using an observer for this would be excessively convoluted.
Here's some code -
it includes an additional hack (fudgeY) because my table has a sticky header that isn't detectable by straightforward means (and handling this automatically would be pretty tedious). Also, it uses decimal (0 to 1) instead of percentage for the required visible fraction. (For my case I need full y, and x isn't relevant).
function intersectRect(r1, r2) {
var r = {};
r.left = r1.left < r2.left ? r2.left : r1.left;
r.top = r1.top < r2.top ? r2.top : r1.top;
r.right = r1.right < r2.right ? r1.right : r2.right;
r.bottom = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
if (r.left < r.right && r.top < r.bottom)
return r;
return null;
}
function innerRect(e) {
var b,r;
b = e.getBoundingClientRect();
r = {};
r.left = b.left;
r.top = b.top;
r.right = b.left + e.clientWidth;
r.bottom = b.top + e.clientHeight;
return r;
}
function isViewable(e, fracX, fracY, fudgeY) {
// ref https://stackoverflow.com/a/37998526
// intersect all the rects and then check the result once
// innerRect: mind the scroll bars
// fudgeY: handle "sticky" thead in parent table. Ugh.
var r, pr, er;
er = e.getBoundingClientRect();
r = er;
for (;;) {
e = e.parentElement;
if (!e)
break;
pr = innerRect(e);
if (fudgeY)
pr.top += fudgeY;
r = intersectRect(r, pr);
if (!r)
return false;
}
if (fracX && ((r.right-r.left) / (er.right-er.left)) < (fracX-0.001))
return false;
if (fracY && ((r.bottom-r.top) / (er.bottom-er.top)) < (fracY-0.001))
return false;
return true;
}

How can I tell if a DOM element is visible in the current viewport?

Is there an efficient way to tell if a DOM element (in an HTML document) is currently visible (appears in the viewport)?
(The question refers to Firefox.)
Now most browsers support getBoundingClientRect method, which has become the best practice. Using an old answer is very slow, not accurate and has several bugs.
The solution selected as correct is almost never precise.
This solution was tested on Internet Explorer 7 (and later), iOS 5 (and later) Safari, Android 2.0 (Eclair) and later, BlackBerry, Opera Mobile, and Internet Explorer Mobile 9.
function isElementInViewport (el) {
// Special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
);
}
How to use:
You can be sure that the function given above returns correct answer at the moment of time when it is called, but what about tracking element's visibility as an event?
Place the following code at the bottom of your <body> tag:
function onVisibilityChange(el, callback) {
var old_visible;
return function () {
var visible = isElementInViewport(el);
if (visible != old_visible) {
old_visible = visible;
if (typeof callback == 'function') {
callback();
}
}
}
}
var handler = onVisibilityChange(el, function() {
/* Your code go here */
});
// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);
/* // Non-jQuery
if (window.addEventListener) {
addEventListener('DOMContentLoaded', handler, false);
addEventListener('load', handler, false);
addEventListener('scroll', handler, false);
addEventListener('resize', handler, false);
} else if (window.attachEvent) {
attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
attachEvent('onload', handler);
attachEvent('onscroll', handler);
attachEvent('onresize', handler);
}
*/
If you do any DOM modifications, they can change your element's visibility of course.
Guidelines and common pitfalls:
Maybe you need to track page zoom / mobile device pinch? jQuery should handle zoom/pinch cross browser, otherwise first or second link should help you.
If you modify DOM, it can affect the element's visibility. You should take control over that and call handler() manually. Unfortunately, we don't have any cross browser onrepaint event. On the other hand that allows us to make optimizations and perform re-check only on DOM modifications that can change an element's visibility.
Never Ever use it inside jQuery $(document).ready() only, because there is no warranty CSS has been applied in this moment. Your code can work locally with your CSS on a hard drive, but once put on a remote server it will fail.
After DOMContentLoaded is fired, styles are applied, but the images are not loaded yet. So, we should add window.onload event listener.
We can't catch zoom/pinch event yet.
The last resort could be the following code:
/* TODO: this looks like a very bad code */
setInterval(handler, 600);
You can use the awesome feature pageVisibiliy of the HTML5 API if you care if the tab with your web page is active and visible.
TODO: this method does not handle two situations:
Overlapping using z-index.
Using overflow-scroll in element's container.
Try something new - The Intersection Observer API explained.
Update: Time marches on and so have our browsers. This technique is no longer recommended and you should use Dan's solution if you do not need to support version of Internet Explorer before 7.
Original solution (now outdated):
This will check if the element is entirely visible in the current viewport:
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
You could modify this simply to determine if any part of the element is visible in the viewport:
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
Update
In modern browsers, you might want to check out the Intersection Observer API which provides the following benefits:
Better performance than listening for scroll events
Works in cross domain iframes
Can tell if an element is obstructing/intersecting another
Intersection Observer is on its way to being a full-fledged standard and is already supported in Chrome 51+, Edge 15+ and Firefox 55+ and is under development for Safari. There's also a polyfill available.
Previous answer
There are some issues with the answer provided by Dan that might make it an unsuitable approach for some situations. Some of these issues are pointed out in his answer near the bottom, that his code will give false positives for elements that are:
Hidden by another element in front of the one being tested
Outside the visible area of a parent or ancestor element
An element or its children hidden by using the CSS clip property
These limitations are demonstrated in the following results of a simple test:
The solution: isElementVisible()
Here's a solution to those problems, with the test result below and an explanation of some parts of the code.
function isElementVisible(el) {
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || document.documentElement.clientWidth,
vHeight = window.innerHeight || document.documentElement.clientHeight,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight)
return false;
// Return true if any of its four corners are visible
return (
el.contains(efp(rect.left, rect.top))
|| el.contains(efp(rect.right, rect.top))
|| el.contains(efp(rect.right, rect.bottom))
|| el.contains(efp(rect.left, rect.bottom))
);
}
Passing test: http://jsfiddle.net/AndyE/cAY8c/
And the result:
Additional notes
This method is not without its own limitations, however. For instance, an element being tested with a lower z-index than another element at the same location would be identified as hidden even if the element in front doesn't actually hide any part of it. Still, this method has its uses in some cases that Dan's solution doesn't cover.
Both element.getBoundingClientRect() and document.elementFromPoint() are part of the CSSOM Working Draft specification and are supported in at least IE 6 and later and most desktop browsers for a long time (albeit, not perfectly). See Quirksmode on these functions for more information.
contains() is used to see if the element returned by document.elementFromPoint() is a child node of the element we're testing for visibility. It also returns true if the element returned is the same element. This just makes the check more robust. It's supported in all major browsers, Firefox 9.0 being the last of them to add it. For older Firefox support, check this answer's history.
If you want to test more points around the element for visibility―ie, to make sure the element isn't covered by more than, say, 50%―it wouldn't take much to adjust the last part of the answer. However, be aware that it would probably be very slow if you checked every pixel to make sure it was 100% visible.
I tried Dan's answer, however, the algebra used to determine the bounds means that the element must be both ≤ the viewport size and completely inside the viewport to get true, easily leading to false negatives. If you want to determine whether an element is in the viewport at all, ryanve's answer is close but the element being tested should overlap the viewport, so try this:
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return rect.bottom > 0 &&
rect.right > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}
We have now a native javascript Intersection Observer API
from which we can detect elements either they are in the viewport or not.
Here is example
const el = document.querySelector('#el')
const observer = new window.IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
console.log('ENTER')
return
}
console.log('LEAVE')
}, {
root: null,
threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
})
observer.observe(el);
body {
height: 300vh;
}
#el {
margin-top: 100vh;
}
<div id="el">this is element</div>
See the source of verge, which uses getBoundingClientRect. It's like:
function inViewport (element) {
if (!element) return false;
if (1 !== element.nodeType) return false;
var html = document.documentElement;
var rect = element.getBoundingClientRect();
return !!rect &&
rect.bottom >= 0 &&
rect.right >= 0 &&
rect.left <= html.clientWidth &&
rect.top <= html.clientHeight;
}
It returns true if any part of the element is in the viewport.
As a public service:
Dan's answer with the correct calculations (element can be > window, especially on mobile phone screens), and correct jQuery testing, as well as adding isElementPartiallyInViewport:
By the way, the difference between window.innerWidth and document.documentElement.clientWidth is that clientWidth/clientHeight doesn't include the scrollbar, while window.innerWidth/Height does.
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
Test-case
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Test</title>
<!--
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="scrollMonitor.js"></script>
-->
<script type="text/javascript">
function isElementPartiallyInViewport(el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);
return (vertInView && horInView);
}
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
// Special bonus for those using jQuery
if (typeof jQuery !== 'undefined' && el instanceof jQuery)
el = el[0];
var rect = el.getBoundingClientRect();
var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
var windowWidth = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.left >= 0)
&& (rect.top >= 0)
&& ((rect.left + rect.width) <= windowWidth)
&& ((rect.top + rect.height) <= windowHeight)
);
}
function fnIsVis(ele)
{
var inVpFull = isElementInViewport(ele);
var inVpPartial = isElementPartiallyInViewport(ele);
console.clear();
console.log("Fully in viewport: " + inVpFull);
console.log("Partially in viewport: " + inVpPartial);
}
// var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
// var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
</script>
</head>
<body>
<div style="display: block; width: 2000px; height: 10000px; background-color: green;">
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
<div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
t
</div>
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br />
<input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
</div>
<!--
<script type="text/javascript">
var element = document.getElementById("myele");
var watcher = scrollMonitor.create(element);
watcher.lock();
watcher.stateChange(function() {
console.log("state changed");
// $(element).toggleClass('fixed', this.isAboveViewport)
});
</script>
-->
</body>
</html>
My shorter and faster version:
function isElementOutViewport(el){
var rect = el.getBoundingClientRect();
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}
And a jsFiddle as required: https://jsfiddle.net/on1g619L/1/
The new Intersection Observer API addresses this question very directly.
This solution will need a polyfill as Safari, Opera and Internet Explorer don't support this yet (the polyfill is included in the solution).
In this solution, there is a box out of view that is the target (observed). When it comes into view, the button at the top in the header is hidden. It is shown once the box leaves the view.
const buttonToHide = document.querySelector('button');
const hideWhenBoxInView = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) { // If not in view
buttonToHide.style.display = "inherit";
} else {
buttonToHide.style.display = "none";
}
});
hideWhenBoxInView.observe(document.getElementById('box'));
header {
position: fixed;
top: 0;
width: 100vw;
height: 30px;
background-color: lightgreen;
}
.wrapper {
position: relative;
margin-top: 600px;
}
#box {
position: relative;
left: 175px;
width: 150px;
height: 135px;
background-color: lightblue;
border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
<button>NAVIGATION BUTTON TO HIDE</button>
</header>
<div class="wrapper">
<div id="box">
</div>
</div>
I found it troubling that there wasn't a jQuery-centric version of the functionality available. When I came across Dan's solution I spied the opportunity to provide something for folks who like to program in the jQuery OO style. It's nice and snappy and works like a charm for me.
Bada bing bada boom
$.fn.inView = function(){
if(!this.length)
return false;
var rect = this.get(0).getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
var all = [];
this.forEach(function(){
all.push( $(this).inView() );
});
return all.indexOf(false) === -1;
};
// Only the class elements in view
$('.some-class').filter(function(){
return $(this).inView();
});
// Only the class elements not in view
$('.some-class').filter(function(){
return !$(this).inView();
});
Usage
$(window).on('scroll',function(){
if( $('footer').inView() ) {
// Do cool stuff
}
});
The simplest solution as the support of Element.getBoundingClientRect() has become perfect:
function isInView(el) {
const box = el.getBoundingClientRect();
return box.top < window.innerHeight && box.bottom >= 0;
}
All answers I've encountered here only check if the element is positioned inside the current viewport. But that doesn't mean that it is visible.
What if the given element is inside a div with overflowing content, and it is scrolled out of view?
To solve that, you'd have to check if the element is contained by all parents.
My solution does exactly that:
It also allows you to specify how much of the element has to be visible.
Element.prototype.isVisible = function(percentX, percentY){
var tolerance = 0.01; //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
if(percentX == null){
percentX = 100;
}
if(percentY == null){
percentY = 100;
}
var elementRect = this.getBoundingClientRect();
var parentRects = [];
var element = this;
while(element.parentElement != null){
parentRects.push(element.parentElement.getBoundingClientRect());
element = element.parentElement;
}
var visibleInAllParents = parentRects.every(function(parentRect){
var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
var visiblePercentageX = visiblePixelX / elementRect.width * 100;
var visiblePercentageY = visiblePixelY / elementRect.height * 100;
return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
});
return visibleInAllParents;
};
This solution ignored the fact that elements may not be visible due to other facts, like opacity: 0.
I have tested this solution in Chrome and Internet Explorer 11.
I find that the accepted answer here is overly complicated for most use cases. This code does the job well (using jQuery) and differentiates between fully visible and partially visible elements:
var element = $("#element");
var topOfElement = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window = $(window);
$window.bind('scroll', function() {
var scrollTopPosition = $window.scrollTop()+$window.height();
var windowScrollTop = $window.scrollTop()
if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
// Element is partially visible (above viewable area)
console.log("Element is partially visible (above viewable area)");
} else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
// Element is hidden (above viewable area)
console.log("Element is hidden (above viewable area)");
} else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
// Element is hidden (below viewable area)
console.log("Element is hidden (below viewable area)");
} else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
// Element is partially visible (below viewable area)
console.log("Element is partially visible (below viewable area)");
} else {
// Element is completely visible
console.log("Element is completely visible");
}
});
Here's my solution. It will work if an element is hidden inside a scrollable container.
Here's a demo (try re-sizing the window to)
var visibleY = function(el){
var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
do {
rect = el.getBoundingClientRect();
if (top <= rect.bottom === false)
return false;
el = el.parentNode;
} while (el != document.body);
// Check it's within the document viewport
return top <= document.documentElement.clientHeight;
};
I only needed to check if it's visible in the Y axis (for a scrolling Ajax load-more-records feature).
I think this is a more functional way to do it.
Dan's answer do not work in a recursive context.
This function solves the problem when your element is inside others scrollable divs by testing any levels recursively up to the HTML tag, and stops at the first false.
/**
* fullVisible=true only returns true if the all object rect is visible
*/
function isReallyVisible(el, fullVisible) {
if ( el.tagName == "HTML" )
return true;
var parentRect=el.parentNode.getBoundingClientRect();
var rect = arguments[2] || el.getBoundingClientRect();
return (
( fullVisible ? rect.top >= parentRect.top : rect.bottom > parentRect.top ) &&
( fullVisible ? rect.left >= parentRect.left : rect.right > parentRect.left ) &&
( fullVisible ? rect.bottom <= parentRect.bottom : rect.top < parentRect.bottom ) &&
( fullVisible ? rect.right <= parentRect.right : rect.left < parentRect.right ) &&
isReallyVisible(el.parentNode, fullVisible, rect)
);
};
The most accepted answers don't work when zooming in Google Chrome on Android. In combination with Dan's answer, to account for Chrome on Android, visualViewport must be used. The following example only takes the vertical check into account and uses jQuery for the window height:
var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
ElTop -= window.visualViewport.offsetTop;
ElBottom -= window.visualViewport.offsetTop;
WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);
/**
* Returns Element placement information in Viewport
* #link https://stackoverflow.com/a/70476497/2453148
*
* #typedef {object} ViewportInfo - Whether the element is…
* #property {boolean} isInViewport - fully or partially in the viewport
* #property {boolean} isPartiallyInViewport - partially in the viewport
* #property {boolean} isInsideViewport - fully inside viewport
* #property {boolean} isAroundViewport - completely covers the viewport
* #property {boolean} isOnEdge - intersects the edge of viewport
* #property {boolean} isOnTopEdge - intersects the top edge
* #property {boolean} isOnRightEdge - intersects the right edge
* #property {boolean} isOnBottomEdge - is intersects the bottom edge
* #property {boolean} isOnLeftEdge - is intersects the left edge
*
* #param el Element
* #return {Object} ViewportInfo
*/
function getElementViewportInfo(el) {
let result = {};
let rect = el.getBoundingClientRect();
let windowHeight = window.innerHeight || document.documentElement.clientHeight;
let windowWidth = window.innerWidth || document.documentElement.clientWidth;
let insideX = rect.left >= 0 && rect.left + rect.width <= windowWidth;
let insideY = rect.top >= 0 && rect.top + rect.height <= windowHeight;
result.isInsideViewport = insideX && insideY;
let aroundX = rect.left < 0 && rect.left + rect.width > windowWidth;
let aroundY = rect.top < 0 && rect.top + rect.height > windowHeight;
result.isAroundViewport = aroundX && aroundY;
let onTop = rect.top < 0 && rect.top + rect.height > 0;
let onRight = rect.left < windowWidth && rect.left + rect.width > windowWidth;
let onLeft = rect.left < 0 && rect.left + rect.width > 0;
let onBottom = rect.top < windowHeight && rect.top + rect.height > windowHeight;
let onY = insideY || aroundY || onTop || onBottom;
let onX = insideX || aroundX || onLeft || onRight;
result.isOnTopEdge = onTop && onX;
result.isOnRightEdge = onRight && onY;
result.isOnBottomEdge = onBottom && onX;
result.isOnLeftEdge = onLeft && onY;
result.isOnEdge = result.isOnLeftEdge || result.isOnRightEdge ||
result.isOnTopEdge || result.isOnBottomEdge;
let isInX =
insideX || aroundX || result.isOnLeftEdge || result.isOnRightEdge;
let isInY =
insideY || aroundY || result.isOnTopEdge || result.isOnBottomEdge;
result.isInViewport = isInX && isInY;
result.isPartiallyInViewport =
result.isInViewport && result.isOnEdge;
return result;
}
Based on dan's solution, I had a go at cleaning up the implementation so that using it multiple times on the same page is easier:
$(function() {
$(window).on('load resize scroll', function() {
addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
addClassToElementInViewport($('.another-thing'), 'animate-thing');
// 👏 repeat as needed ...
});
function addClassToElementInViewport(element, newClass) {
if (inViewport(element)) {
element.addClass(newClass);
}
}
function inViewport(element) {
if (typeof jQuery === "function" && element instanceof jQuery) {
element = element[0];
}
var elementBounds = element.getBoundingClientRect();
return (
elementBounds.top >= 0 &&
elementBounds.left >= 0 &&
elementBounds.bottom <= $(window).height() &&
elementBounds.right <= $(window).width()
);
}
});
The way I'm using it is that when the element scrolls into view, I'm adding a class that triggers a CSS keyframe animation. It's pretty straightforward and works especially well when you've got like 10+ things to conditionally animate on a page.
Most of the usages in previous answers are failing at these points:
-When any pixel of an element is visible, but not "a corner",
-When an element is bigger than viewport and centered,
-Most of them are checking only for a singular element inside a document or window.
Well, for all these problems I've a solution and the plus sides are:
-You can return visible when only a pixel from any sides shows up and is not a corner,
-You can still return visible while element bigger than viewport,
-You can choose your parent element or you can automatically let it choose,
-Works on dynamically added elements too.
If you check the snippets below you will see the difference in using overflow-scroll in element's container will not cause any trouble and see that unlike other answers here even if a pixel shows up from any side or when an element is bigger than viewport and we are seeing inner pixels of the element it still works.
Usage is simple:
// For checking element visibility from any sides
isVisible(element)
// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)
// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice
A demonstration without crossSearchAlgorithm which is usefull for elements bigger than viewport check element3 inner pixels to see:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent, so it will be element's parent
if (isVisible(elementList[i])) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
You see, when you are inside the element3 it fails to tell if it's visible or not, because we are only checking if the element is visible from sides or corners.
And this one includes crossSearchAlgorithm which allows you to still return visible when the element is bigger than the viewport:
function isVisible(element, parent, crossSearchAlgorithm) {
var rect = element.getBoundingClientRect(),
prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
efp = function (x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
return false;
}
var flag = false;
// Return true if left to right any border pixel reached
for (var x = rect.left; x < rect.right; x++) {
if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
flag = true;
break;
}
}
// Return true if top to bottom any border pixel reached
if (flag == false) {
for (var y = rect.top; y < rect.bottom; y++) {
if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
flag = true;
break;
}
}
}
if(csa) {
// Another algorithm to check if element is centered and bigger than viewport
if (flag == false) {
var x = rect.left;
var y = rect.top;
// From top left to bottom right
while(x < rect.right || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x < rect.right) { x++; }
if(y < rect.bottom) { y++; }
}
if (flag == false) {
x = rect.right;
y = rect.top;
// From top right to bottom left
while(x > rect.left || y < rect.bottom) {
if (element.contains(efp(x,y))) {
flag = true;
break;
}
if(x > rect.left) { x--; }
if(y < rect.bottom) { y++; }
}
}
}
}
return flag;
}
// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
var elementList = document.getElementsByClassName("element");
var console = document.getElementById('console');
for (var i=0; i < elementList.length; i++) {
// I did not define parent so it will be element's parent
// and it will do crossSearchAlgorithm
if (isVisible(elementList[i],null,true)) {
console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
break;
} else {
console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
}
}
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
var newElement = document.createElement("div");
newElement.id = "element" + i;
newElement.classList.add("element");
document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
width: 300px;
height: 100px;
background-color: lightblue;
overflow-y: auto;
padding-top: 150px;
margin: 45px;
}
.element {
margin: 400px;
width: 400px;
height: 320px;
background-color: green;
}
#element3 {
position: relative;
margin: 40px;
width: 720px;
height: 520px;
background-color: green;
}
#element3::before {
content: "";
position: absolute;
top: -10px;
left: -10px;
margin: 0px;
width: 740px;
height: 540px;
border: 5px dotted green;
background: transparent;
}
<div id="console"></div>
<div id="container">
<div id="element1" class="element"></div>
<div id="element2" class="element"></div>
<div id="element3" class="element"></div>
</div>
JSFiddle to play with: http://jsfiddle.net/BerkerYuceer/grk5az2c/
This code is made for more precise information if any part of the element is shown in the view or not. For performance options or only vertical slides, do not use this! This code is more effective in drawing cases.
As simple as it can get, IMO:
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
return Math.abs(coords.top) <= coords.height;
}
Here is a function that tells if an element is in visible in the current viewport of a parent element:
function inParentViewport(el, pa) {
if (typeof jQuery === "function"){
if (el instanceof jQuery)
el = el[0];
if (pa instanceof jQuery)
pa = pa[0];
}
var e = el.getBoundingClientRect();
var p = pa.getBoundingClientRect();
return (
e.bottom >= p.top &&
e.right >= p.left &&
e.top <= p.bottom &&
e.left <= p.right
);
}
A better solution:
function getViewportSize(w) {
var w = w || window;
if(w.innerWidth != null)
return {w:w.innerWidth, h:w.innerHeight};
var d = w.document;
if (document.compatMode == "CSS1Compat") {
return {
w: d.documentElement.clientWidth,
h: d.documentElement.clientHeight
};
}
return { w: d.body.clientWidth, h: d.body.clientWidth };
}
function isViewportVisible(e) {
var box = e.getBoundingClientRect();
var height = box.height || (box.bottom - box.top);
var width = box.width || (box.right - box.left);
var viewport = getViewportSize();
if(!height || !width)
return false;
if(box.top > viewport.h || box.bottom < 0)
return false;
if(box.right < 0 || box.left > viewport.w)
return false;
return true;
}
I had the same question and figured it out by using getBoundingClientRect().
This code is completely 'generic' and only has to be written once for it to work (you don't have to write it out for each element that you want to know is in the viewport).
This code only checks to see if it is vertically in the viewport, not horizontally. In this case, the variable (array) 'elements' holds all the elements that you are checking to be vertically in the viewport, so grab any elements you want anywhere and store them there.
The 'for loop', loops through each element and checks to see if it is vertically in the viewport. This code executes every time the user scrolls! If the getBoudingClientRect().top is less than 3/4 the viewport (the element is one quarter in the viewport), it registers as 'in the viewport'.
Since the code is generic, you will want to know 'which' element is in the viewport. To find that out, you can determine it by custom attribute, node name, id, class name, and more.
Here is my code (tell me if it doesn't work; it has been tested in Internet Explorer 11, Firefox 40.0.3, Chrome Version 45.0.2454.85 m, Opera 31.0.1889.174, and Edge with Windows 10, [not Safari yet])...
// Scrolling handlers...
window.onscroll = function(){
var elements = document.getElementById('whatever').getElementsByClassName('whatever');
for(var i = 0; i != elements.length; i++)
{
if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
elements[i].getBoundingClientRect().top > 0)
{
console.log(elements[i].nodeName + ' ' +
elements[i].className + ' ' +
elements[i].id +
' is in the viewport; proceed with whatever code you want to do here.');
}
};
This checks if an element is at least partially in view (vertical dimension):
function inView(element) {
var box = element.getBoundingClientRect();
return inViewBox(box);
}
function inViewBox(box) {
return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}
function getWindowSize() {
return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}
This is the easy and small solution that has worked for me.
Example: You want to see if the element is visible in the parent element that has overflow scroll.
$(window).on('scroll', function () {
var container = $('#sidebar');
var containerHeight = container.height();
var scrollPosition = $('#row1').offset().top - container.offset().top;
if (containerHeight < scrollPosition) {
console.log('not visible');
} else {
console.log('visible');
}
})
All the answers here are determining if the element is fully contained within the viewport, not just visible in some way. For example, if only half of an image is visible at the bottom of the view, the solutions here will fail, considering that "outside".
I had a use case where I'm doing lazy loading via IntersectionObserver, but due to animations that occur during pop-in, I didn't want to observe any images that were already intersected on page load. To do that, I used the following code:
const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
(0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));
This is basically checking to see if either the top or bottom bound is independently in the viewport. The opposite end may be outside, but as long as one end is in, it's "visible" at least partially.
I use this function (it only checks if the y is inscreen since most of the time the x is not needed)
function elementInViewport(el) {
var elinfo = {
"top":el.offsetTop,
"height":el.offsetHeight,
};
if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
return false;
} else {
return true;
}
}
Here is a snippet to check if the given element is fully visible in its parent:
export const visibleInParentViewport = (el) => {
const elementRect = el.getBoundingClientRect();
const parentRect = el.parentNode.getBoundingClientRect();
return (
elementRect.top >= parentRect.top &&
elementRect.right >= parentRect.left &&
elementRect.top + elementRect.height <= parentRect.bottom &&
elementRect.left + elementRect.width <= parentRect.right
);
}
const isHTMLElementInView = (element: HTMLElement) => {
const rect = element?.getBoundingClientRect()
if (!rect) return
return rect.top <= window.innerHeight && rect.bottom >= 0
}
This function checks if the element is in the viewport on vertical level.
Domysee's answer https://stackoverflow.com/a/37998526 is close to correct.
Many examples use "completely contained in the viewport" and his code uses percentages to allow for partially visible. His code also addresses the "is a parent clipping the view" question, which most examples ignore.
One missing element is the impact of the parent's scrollbars - getBoundingClientRect returns the outer rectangle of the parent, which includes the scroll bars, not the inner rectangle, which doesn't. A child can hide behind the parent scroll bar and be considered visible when it isn't.
The recommended observer pattern isn't appropriate for my use case: using the arrow keys to change the currently selected row in a table, and make sure the new selection is visible. Using an observer for this would be excessively convoluted.
Here's some code -
it includes an additional hack (fudgeY) because my table has a sticky header that isn't detectable by straightforward means (and handling this automatically would be pretty tedious). Also, it uses decimal (0 to 1) instead of percentage for the required visible fraction. (For my case I need full y, and x isn't relevant).
function intersectRect(r1, r2) {
var r = {};
r.left = r1.left < r2.left ? r2.left : r1.left;
r.top = r1.top < r2.top ? r2.top : r1.top;
r.right = r1.right < r2.right ? r1.right : r2.right;
r.bottom = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
if (r.left < r.right && r.top < r.bottom)
return r;
return null;
}
function innerRect(e) {
var b,r;
b = e.getBoundingClientRect();
r = {};
r.left = b.left;
r.top = b.top;
r.right = b.left + e.clientWidth;
r.bottom = b.top + e.clientHeight;
return r;
}
function isViewable(e, fracX, fracY, fudgeY) {
// ref https://stackoverflow.com/a/37998526
// intersect all the rects and then check the result once
// innerRect: mind the scroll bars
// fudgeY: handle "sticky" thead in parent table. Ugh.
var r, pr, er;
er = e.getBoundingClientRect();
r = er;
for (;;) {
e = e.parentElement;
if (!e)
break;
pr = innerRect(e);
if (fudgeY)
pr.top += fudgeY;
r = intersectRect(r, pr);
if (!r)
return false;
}
if (fracX && ((r.right-r.left) / (er.right-er.left)) < (fracX-0.001))
return false;
if (fracY && ((r.bottom-r.top) / (er.bottom-er.top)) < (fracY-0.001))
return false;
return true;
}

Categories

Resources