如何判断DOM元素在当前视口中是否可见?

是否有一种有效的方法来判断DOM元素(在超文本标记语言文档中)当前是否可见(出现在视口中)?

(这个问题指的是Firefox。

751528 次浏览

更新时间:时间在流逝,我们的浏览器也是如此。这种技术不再被推荐,如果您不需要支持7之前的Internet Explorer版本,您应该使用丹的解决方案

原始解决方案(现已过时):

这将检查元素是否在当前视口中完全可见:

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));}

你可以简单地修改它以确定元素的任何部分是否在视口中可见:

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);}

现在大多数浏览器支持获取边界客户端Rect方法,这已成为最佳实践。使用旧答案非常慢,不准确有几个错误

选择正确的解决方案是几乎从不精确


此解决方案在Internet Explorer 7(及更高版本)、iOS5(及更高版本)Safari、android2.0(Eclair)及更高版本、BlackBerry、Opera Mobile和Internet Explorer Mobile9上进行了测试。


function isElementInViewport (el) {
// Special bonus for those using jQueryif (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() */);}

如何使用:

您可以确定上面给出的函数在被调用时返回正确的答案,但是跟踪元素作为事件的可见性呢?

将以下代码放在<body>标签的底部:

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-jQueryif (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);}*/

如果您进行任何DOM修改,它们当然可以更改元素的可见性。

指南和常见陷阱:

也许你需要跟踪页面缩放/移动设备捏? jQuery应该处理缩放/缩小跨浏览器,否则第一第二链接应该可以帮助您。

如果你修改DOM,它会影响元素的可见性。你应该控制它并手动调用handler()。不幸的是,我们没有任何跨浏览器onrepaint事件。另一方面,这允许我们进行优化并仅对可能改变元素可见性的DOM修改执行重新检查。

永远不会仅在jQuery$(文档).就绪()中使用它,因为此时没有保修CSS已应用。您的代码可以在本地使用硬盘上的CSS,但一旦放在远程服务器上,它就会失败。

触发DOMContentLoaded后,样式应用,但图像还没有加载。所以,我们应该添加window.onload事件侦听器。

我们还不能捕捉变焦/捏事件。

最后的手段可能是以下代码:

/* TODO: this looks like a very bad code */setInterval(handler, 600);

如果您关心网页的选项卡是否处于活动状态且可见,您可以使用HTML5 API的出色功能页面可见性

待办事项:此方法不处理两种情况:

查看边缘的源代码,它使用获取边界客户端Rect。就像:

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;}

如果元素的任何部分在视口中,则返回true

更好的解决方案:

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;}

更新

在现代浏览器中,您可能希望查看交叉点观察者API,它提供以下好处:

  • 性能优于监听滚动事件
  • 适用于跨域iFrame
  • 可以判断一个元素是否阻碍/相交另一个元素

交叉点观察者正在成为一个成熟的标准,并且已经在Chrome51+,Edge 15+和Firefox 55+中得到支持,并且正在为Safari开发。还有一个聚填充可用。


以前的答复

答案由Dan提供存在一些问题,可能使其不适合某些情况。他在底部的回答中指出了其中一些问题,他的代码会对以下元素给出误报:

  • 被另一个元素隐藏在被测试的元素前面
  • 在父元素或祖先元素的可见区域之外
  • 使用CSSclip属性隐藏的元素或其子元素

这些限制在a简单的测试的以下结果中得到了证明:

测试失败,使用isElementInViewport

解决方案:isElementVisible()

下面是这些问题的解决方案,下面是测试结果和对代码某些部分的解释。

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 viewportif (rect.right < 0 || rect.bottom < 0|| rect.left > vWidth || rect.top > vHeight)return false;
// Return true if any of its four corners are visiblereturn (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)));}

通过测试:http://jsfiddle.net/AndyE/cAY8c/

而结果:

通过测试,使用isElementViable

补充说明

然而,这种方法并非没有自己的局限性。例如,一个被测试的元素的z指数低于同一位置的另一个元素,即使前面的元素实际上并没有隐藏它的任何部分,也会被识别为隐藏。尽管如此,这种方法在某些情况下有其用途,Dan的解决方案没有涵盖。

element.getBoundingClientRect()document.elementFromPoint()都是CSSOM工作草案规范的一部分,并且在很长一段时间内至少在IE6及更高版本和大多数桌面浏览器中得到支持(尽管不是完美的)。有关更多信息,请参阅这些函数的Quirksmode

contains()用于查看document.elementFromPoint()返回的元素是否是我们正在测试可见性的元素的子节点。如果返回的元素是相同的元素,它也会返回true。这只会使检查更加健壮。所有主要浏览器都支持它,Firefox 9.0是最后添加它的浏览器。对于较旧的Firefox支持,请检查此答案的历史记录。

如果你想测试元素周围的更多点的可见性-即,确保元素的覆盖范围不超过,比如说,50%-调整答案的最后一部分不需要太多。然而,请注意,如果你检查每个像素以确保它是100%可见,它可能会非常慢。

我尝试了丹的回答,然而,用于确定边界的代数意味着元素必须既≤视口大小又完全在视口内才能得到true,这很容易导致假阴性。如果你想确定一个元素是否在视口中,ryanve的回答很接近,但被测试的元素应该与视口重叠,所以试试这个:

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() */;}

这是我的解决方案。如果一个元素隐藏在可滚动容器中,它将工作。

这是一个演示(尝试重新调整窗口大小)

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 viewportreturn top <= document.documentElement.clientHeight;};

我只需要检查它是否在Y轴上可见(对于滚动的Ajax load-more记录功能)。

更短更快的版本:

function isElementOutViewport(el){var rect = el.getBoundingClientRect();return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;}

需要一个jsFiddle:https://jsfiddle.net/on1g619L/1/

作为公共服务:
Dan的答案用正确的计算(元素可以>窗口,尤其是在手机屏幕上),和正确的jQuery测试,以及添加isElementParallyInViewport:

顺便说一下,window.innerWidth和document.documentElement.clientWidth之间的0是clientWidth/clientHeight不包含滚动条,而window.innerWidth/Height包含滚动条。

function isElementPartiallyInViewport(el){// Special bonus for those using jQueryif (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-overlapvar 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-viewportfunction isElementInViewport (el){// Special bonus for those using jQueryif (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);}

测试用例

<!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 jQueryif (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-overlapvar 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-viewportfunction isElementInViewport (el){// Special bonus for those using jQueryif (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>

基于丹的解决方案,我尝试清理实现,以便在同一页面上多次使用它更容易:

$(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());}
});

我使用它的方式是,当元素滚动到视图中时,我添加了一个触发CSS关键帧动画的类。它非常简单,当你在页面上有10多个东西要有条件地动画时,效果特别好。

这将检查元素是否至少部分处于视图中(垂直维度):

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}}

我发现这里接受的答案对于大多数用例来说过于复杂。这段代码做得很好(使用jQuery),并区分了完全可见和部分可见的元素:

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 visibleconsole.log("Element is completely visible");}});

我认为这是一种更实用的方法。丹的回答在递归上下文中不起作用。

此函数解决了当您的元素在其他可滚动div中时的问题,方法是递归测试任何级别直到超文本标记语言标记,并在第一个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));};

我发现没有可用的以jQuery为中心的功能版本令人不安。当我遇到丹的解决方案时,我发现有机会为喜欢用jQuery OO风格编程的人提供一些东西。它既漂亮又快捷,对我来说就像一种魅力。

Bada bada bada繁荣

$.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();});

用法

$(window).on('scroll',function(){
if( $('footer').inView() ) {// Do cool stuff}});

我有同样的问题,并通过使用getBoundingClientRect()解决了这个问题。

这段代码完全是“通用的”,只需要编写一次就可以工作(你不必为你想知道的每个元素都写出来)。

这段代码只检查它是否垂直在视口中,不是水平。在这种情况下,变量(数组)“元素”保存了你正在检查垂直在视口中的所有元素,所以在任何地方获取任何你想要的元素并将它们存储在那里。

'for循环',循环遍历每个元素并检查它是否垂直在视口中。此代码执行每次用户滚动!如果getBoudingClientRect(). top小于视口的3/4(元素在视口中的四分之一),它注册为'在视口中'。

由于代码是通用的,因此您需要知道“哪个”元素在视口中。要找到答案,您可以通过自定义属性、节点名称、id、类名等来确定它。

这是我的代码(告诉我它是否不起作用;它已经在Internet Explorer 11,Firefox 40.0.3,Chrome版本45.0.2454.85 m,Opera 31.0.1889.174和Edge with Windows 10中进行了测试,[尚未Safari])…

// 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.');}};

对于一个类似的挑战,我真的很喜欢这一要点,它为如果需要滚动查看()公开了一个多边形填充。

所有必要的功夫需要回答是在这个块:

var parent = this.parentNode,parentComputedStyle = window.getComputedStyle(parent, null),parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),alignWithTop = overTop && !overBottom;

this指的是您想知道它是overTop还是overBottom的元素-您应该得到漂移…

我在这里遇到的所有答案都只检查元素是否为位于当前视口内。但那是并不意味着它是可见的
如果给定元素在内容溢出的div中,并且它被滚动出视图怎么办?

要解决这个问题,您必须检查元素是否包含在所有父元素中。
我的解决方案就是这样:

它还允许您指定元素的多少必须可见。

Element.prototype.isVisible = function(percentX, percentY){var tolerance = 0.01;   //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimalsif(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;};

此解决方案忽略了元素可能由于其他事实(如opacity: 0)而不可见的事实。

我已经在Chrome和Internet Explorer 11中测试了这个解决方案。

我使用这个函数(它只检查y是否在屏幕上,因为大多数时候不需要x)

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;}
}

这是对我有效的简单而小的解决方案。

示例:您想查看该元素是否在具有溢出滚动的父元素中可见。

$(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');}})

这是一个函数,它告诉一个元素是否在父母元素的当前视口中可见:

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);}

新的交叉点观察者API非常直接地解决了这个问题。

此解决方案将需要一个PolyFill,因为Safari、Opera和Internet Explorer还不支持此功能(解决方案中包含了PolyFill)。

在此解决方案中,有一个显示在视图之外的框是目标(观察到的)。当它进入视图时,标题顶部的按钮被隐藏。一旦框离开视图,它就会显示出来。

const buttonToHide = document.querySelector('button');
const hideWhenBoxInView = new IntersectionObserver((entries) => {if (entries[0].intersectionRatio <= 0) { // If not in viewbuttonToHide.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>

就像它可以得到的那样简单,IMO:

function isVisible(elem) {var coords = elem.getBoundingClientRect();return Math.abs(coords.top) <= coords.height;}

最简单的解决方案作为Element.getBoundingClientRect()的支持有变得完美

function isInView(el) {const box = el.getBoundingClientRect();return box.top < window.innerHeight && box.bottom >= 0;}

以前答案中的大多数用法在以下几点都失败了:

-当元素的任何像素可见,但不是“一个角落”时,

当一个元素是大于视口并居中时,

-它们中的大多数仅检查单数元素在文档或窗口中

好吧,对于所有这些问题,我有一个解决方案,好处是:

-您可以返回visible时,只有一个像素从任何方面出现,而不是一个角落,

-你仍然可以返回visible,而元素大于视口,

-您可以选择您的parent element,也可以自动让它选择,

-也适用于动态添加元素

如果你检查下面的片段,你会看到在元素的容器中使用overflow-scroll的区别不会造成任何麻烦,并且看到不像这里的其他答案即使一个像素从任何一边显示出来,或者当一个元素大于viewport并且我们看到元素的内部像素它仍然有效。

用法很简单:

// For checking element visibility from any sidesisVisible(element)
// For checking elements visibility in a parent you would like to checkvar parent = document; // Assuming you check if 'element' inside 'document'isVisible(element, parent)
// For checking elements visibility even if it's bigger than viewportisVisible(element, null, true) // Without parent choiceisVisible(element, parent, true) // With parent choice

一个没有crossSearchAlgorithm的演示,对于大于viewport check element3内部像素的元素很有用:

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 viewportif (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 reachedfor (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 reachedif (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 viewportif (flag == false) {var x = rect.left;var y = rect.top;// From top left to bottom rightwhile(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 leftwhile(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 visibilitydocument.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 parentif (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 elementsfor(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>

你看,当你是元素内部3时,它无法判断它是否可见,因为我们只检查元素是否从双方角落可见。

这个包含crossSearchAlgorithm,它允许您在元素大于视口时仍然返回visible

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 viewportif (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 reachedfor (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 reachedif (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 viewportif (flag == false) {var x = rect.left;var y = rect.top;// From top left to bottom rightwhile(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 leftwhile(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 visibilitydocument.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 crossSearchAlgorithmif (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 elementsfor(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玩:http://jsfiddle.net/BerkerYuceer/grk5az2c/

如果元素的任何部分是否显示在视图中,则此代码用于提供更精确的信息。对于性能选项或仅垂直幻灯片,请勿使用此代码!此代码在绘图案例中更有效。

在Android上放大GoogleChrome时,最被接受的答案不起作用。结合丹的回答,要考虑Android上的Chrome,必须使用可视化视图。以下示例仅考虑纵向检验,并使用jQuery作为窗口高度:

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);

这里的所有答案都决定了元素是否完全包含在视口中,而不仅仅是以某种方式可见。例如,如果在视图底部只有一半图像可见,考虑到“外部”,这里的解决方案将失败。

我有一个用例,我通过IntersectionObserver进行延迟加载,但由于弹出过程中发生的动画,我不想观察任何在页面加载时已经相交的图像。为此,我使用了以下代码:

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));

这基本上是检查顶部或底部边界是否独立在视口中。相反的一端可能在外面,但只要一端在,它至少部分“可见”。

这是一个代码片段,用于检查给定元素在其父元素中是否完全可见:

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);}

我们现在有了一个原生javascript交叉点观察者API我们可以从中检测元素,无论它们是否在视口中。

这是一个例子

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>

多米尼克的答案https://stackoverflow.com/a/37998526接近正确。

许多示例使用“完全包含在视口中”,他的代码使用百分比来允许部分可见。他的代码还解决了大多数示例忽略的“是父级剪辑视图”问题。

缺少一个元素是父滚动条的影响-getBoundingClientRect返回父滚动条的外部矩形,其中包括滚动条,而不是内部矩形,后者不包含。子滚动条可以隐藏在父滚动条后面,当它不可见时被认为是可见的。

推荐的观察者模式不适合我的用例:使用箭头键更改表中当前选定的行,并确保新选择可见。为此使用观察者会过于复杂。

这里有一些代码-

它包括一个额外的hack(fudgeY),因为我的表有一个无法通过直接方式检测到的粘性标头(并且自动处理这个会非常乏味)。此外,它使用十进制(0到1)而不是所需可见分数的百分比。(对于我的情况,我需要完整的y,x不相关)。

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;}

门户信息

/*** 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;}
 const isHTMLElementInView = (element: HTMLElement) => {const rect = element?.getBoundingClientRect()
if (!rect) returnreturn rect.top <= window.innerHeight && rect.bottom >= 0}

此函数检查元素是否在垂直级别的视口中。