是否有一种有效的方法来判断 DOM 元素(在 HTML 文档中)当前是否可见(出现在视口中)?
(问题指的是 Firefox。)
现在most browsers支持getBoundingClientRect方法,这已成为最佳实践。使用旧答案非常慢,not accurate 和 has several bugs。
选择为正确的解决方案是 almost never precise。
此解决方案已在 Internet Explorer 7(及更高版本)、iOS 5(及更高版本)Safari、Android 2.0 (Eclair) 及更高版本、BlackBerry、Opera Mobile 和 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() */
);
}
如何使用:
您可以确定上面给出的函数在调用它时会返回正确的答案,但是跟踪元素作为事件的可见性呢?
将以下代码放在 <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-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);
}
*/
如果您进行任何 DOM 修改,它们当然可以改变您元素的可见性。
指导方针和常见陷阱:
也许您需要跟踪页面缩放/移动设备捏合? jQuery 应该处理 zoom/pinch 跨浏览器,否则 first 或 second 链接应该可以帮助您。
如果您修改 DOM,它可能会影响元素的可见性。您应该控制它并手动调用 handler()
。很遗憾,我们没有任何跨浏览器 onrepaint
事件。另一方面,这允许我们进行优化并仅对可能改变元素可见性的 DOM 修改执行重新检查。
永远不会仅在 jQuery $(document).ready() 中使用它,因为此时 there is no warranty CSS has been applied。您的代码可以在本地与硬盘上的 CSS 一起工作,但一旦放在远程服务器上,它就会失败。
DOMContentLoaded
被触发后,styles are applied,但 the images are not loaded yet。所以,我们应该添加 window.onload
事件监听器。
我们还不能捕捉缩放/捏合事件。
最后的手段可能是以下代码:
/* TODO: this looks like a very bad code */
setInterval(handler, 600);
如果您关心您的网页的标签是否处于活动状态且可见,您可以使用 HTML5 API 的出色功能 pageVisibiliy。
TODO:此方法不处理两种情况:
使用 z-index 进行重叠。
在元素的容器中使用溢出滚动。
尝试一些新的东西 - Intersection Observer API 解释。
更新:时间在进步,我们的浏览器也在进步。 不再推荐使用此技术,如果您不需要支持 7 之前的 Internet Explorer 版本,则应使用 Dan's solution。
原始解决方案(现已过时):
这将检查元素是否在当前视口中完全可见:
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
);
}
getBoundingClientRect
专门用于查找元素坐标...我们为什么不使用它?
更新
在现代浏览器中,您可能需要查看具有以下优势的 Intersection Observer API:
比监听滚动事件更好的性能
适用于跨域 iframe
可以判断一个元素是否阻碍/与另一个元素相交
Intersection Observer 正在成为一个成熟的标准,并且已经在 Chrome 51+、Edge 15+ 和 Firefox 55+ 中得到支持,并且正在为 Safari 开发。还有一个 polyfill 可用。
上一个答案
answer provided by Dan 存在一些问题,可能使其不适用于某些情况。他在底部附近的回答中指出了其中一些问题,他的代码将对以下元素给出误报:
被正在测试的元素前面的另一个元素隐藏
在父元素或祖先元素的可见区域之外
使用 CSS 剪辑属性隐藏的元素或其子元素
simple test 的以下结果证明了这些限制:
https://i.stack.imgur.com/xdo9l.png
解决方案: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 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))
);
}
通过测试:http://jsfiddle.net/AndyE/cAY8c/
结果:
https://i.stack.imgur.com/Yg3u7.png
补充说明
然而,这种方法并非没有其自身的局限性。例如,一个被测试的元素的 z-index 低于同一位置的另一个元素,即使前面的元素实际上并没有隐藏它的任何部分,它也会被标识为隐藏。尽管如此,这种方法在 Dan 的解决方案未涵盖的某些情况下仍有其用途。
element.getBoundingClientRect()
和 document.elementFromPoint()
都是 CSSOM 工作草案规范的一部分,并且至少在 IE 6 及更高版本以及大多数桌面浏览器中得到了很长时间的支持(尽管并不完美)。有关详细信息,请参阅 Quirksmode on these functions。
contains()
用于查看 document.elementFromPoint()
返回的元素是否是我们正在测试可见性的元素的子节点。如果返回的元素是相同的元素,它也会返回 true。这只会使检查更加健壮。所有主流浏览器都支持它,Firefox 9.0 是最后一个添加它的浏览器。对于较旧的 Firefox 支持,请查看此答案的历史记录。
如果您想测试元素周围更多点的可见性——即确保元素不被超过 50% 覆盖——调整答案的最后部分并不需要太多。但是,请注意,如果您检查每个像素以确保其 100% 可见,它可能会非常慢。
element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
我尝试了 Dan's answer,但是,用于确定边界的代数意味着元素必须既 ≤ 视口大小并且完全在视口内才能获得 true
,容易导致误报。如果你想确定一个元素是否在视口中,ryanve's answer 是关闭的,但被测试的元素应该与视口重叠,所以试试这个:
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() */;
}
请参阅使用 getBoundingClientRect 的 verge 的来源。就像是:
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;
}
如果元素的 any 部分在视口中,则返回 true
。
作为一项公共服务:Dan 的正确计算(元素可以是 > 窗口,尤其是在手机屏幕上)和正确的 jQuery 测试以及添加 isElementPartiallyInViewport 的答案:
顺便说一句,window.innerWidth 和 document.documentElement.clientWidth 之间的 the difference 是 clientWidth/clientHeight 不包含滚动条,而 window.innerWidth/Height 包含。
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);
}
测试用例
<!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>
isElementPartiallyInViewport
也非常有用。好东西。
我们现在有一个原生 javascript Intersection Observer 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, // 设置偏移量 0.1 表示如果视口中至少 10% 的元素触发 }) observer.observe(el);身体{身高:300vh; } #el { 边距顶部:100vh; }
const observer = new window.IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { entry.target.classList.add("viewport__active"); return; } entry.target.classList.remove("viewport__active"); }, { root: null, threshold: 0.4 // 0.0 - 1.0 } );
我更短更快的版本:
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/
我发现没有以 jQuery 为中心的可用功能版本令人不安。当我遇到 Dan's solution 时,我发现有机会为喜欢以 jQuery OO 风格编程的人们提供一些东西。它既漂亮又活泼,对我来说就像一种魅力。
巴达兵巴达热潮
$.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
}
});
新的 Intersection Observer API 非常直接地解决了这个问题。
这个解决方案需要一个 polyfill,因为 Safari、Opera 和 Internet Explorer 还不支持这个(polyfill 包含在解决方案中)。
在这个解决方案中,有一个看不见的盒子是目标(观察到的)。当它进入视图时,标题顶部的按钮是隐藏的。一旦盒子离开视图,它就会显示出来。
const buttonToHide = document.querySelector('button'); const hideWhenBoxInView = new IntersectionObserver((entries) => { if (entries[0].intersectionRatio <= 0) { // 如果不在视图中 buttonToHide.style.display = "inherit"; } else { buttonToHide.style.display = “没有任何”; } }); hideWhenBoxInView.observe(document.getElementById('box'));标题{位置:固定;顶部:0;宽度:100vw;高度:30px;背景颜色:浅绿色; } .wrapper { 位置:相对;边距顶部:600px; } #box { 位置:相对;左:175px;宽度:150px;高度:135px;背景颜色:浅蓝色;边框:2px 实心; }
<!DOCTYPE html>
添加到 HTML 来在 safari 上工作
IntersectionObserver
是一项实验性功能(将来可能会更改)。
IntersectionObserver
仅在目标相对于根移动后触发回调。
observe
事件会立即触发,告诉您被跟踪元素的当前交集状态。所以,在某种程度上 - 它解决了。
作为Element.getBoundingClientRect()支持的最简单的解决方案有become perfect:
function isInView(el) {
const box = el.getBoundingClientRect();
return box.top < window.innerHeight && box.bottom >= 0;
}
$(window).on('scroll', function(){ if(isInView($('.fader').get(0))) {} else {} });
我在这里遇到的所有答案只检查元素是否位于当前视口内。但这并不意味着它是可见的。如果给定元素位于内容溢出的 div 中,并且它被滚动到视图之外怎么办?
为了解决这个问题,您必须检查该元素是否被所有父母所包含。我的解决方案正是这样做的:
它还允许您指定元素的可见程度。
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;
};
此解决方案忽略了元素可能由于其他事实(例如 opacity: 0
)而不可见的事实。
我已经在 Chrome 和 Internet Explorer 11 中测试了这个解决方案。
visibleRectOfElement(el) => {top: 15, left: 45, right: 550, bottom: 420}
我发现对于大多数用例来说,这里接受的答案过于复杂。这段代码很好地完成了这项工作(使用 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 visible
console.log("Element is completely visible");
}
});
$window = $(window)
。
我认为这是一种更实用的方法。 Dan's answer 在递归上下文中不起作用。
当您的元素在其他可滚动的 div 中时,此函数通过递归测试任何级别直到 HTML 标记来解决问题,并在第一个 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)
);
};
这是我的解决方案。如果元素隐藏在可滚动容器中,它将起作用。
Here's a demo(尝试重新调整窗口大小)
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;
};
我只需要检查它是否在 Y 轴上可见(对于滚动 Ajax 加载更多记录功能)。
在 Android 上放大 Google Chrome 时,最接受的答案不起作用。与 Dan's answer 结合使用时,要考虑 Android 上的 Chrome,必须使用 visualViewport。以下示例仅考虑垂直检查并使用 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);
基于 dan's solution,我尝试清理实现,以便在同一页面上多次使用它更容易:
$(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 多个有条件的动画时,它非常简单,并且效果特别好。
$window = $(window)
以前答案中的大多数用法在这些方面都失败了:
- 当一个元素的任何像素可见,但不是“角落”时, - 当一个元素大于视口并居中时, - 他们中的大多数只检查文档或窗口中的单个元素。
好吧,对于所有这些问题,我都有一个解决方案,而且好处是:
- 当只有来自任何一侧的像素出现并且不是角落时,您可以返回可见, - 当元素大于视口时,您仍然可以返回可见, - 您可以选择父元素,也可以自动让它选择, - 适用于也动态添加元素。
如果您检查下面的片段,您会发现在元素容器中使用 overflow-scroll
的区别不会造成任何问题,并且即使显示像素,与此处的其他答案不同从任何一侧或当元素大于视口并且我们看到元素的内部像素时仍然有效。
用法很简单:
// 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
没有 crossSearchAlgorithm
的演示,对于大于视口检查 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) }; // 如果不在视口中,则返回 false if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) { return false ; } 变量标志 = 假; // 如果从左到右到达任何边界像素,则返回 true (var x = rect.left; x < rect.right; x++) { if (element.contains(efp(rect.top, x)) || element.contains (efp(rect.bottom, x))) { flag = true;休息; } } // 如果从上到下到达任何边框像素,则返回 true if (flag == false) { for (var y = rect.top; y < rect.bottom; y++) { if (element.contains(efp(rect.左, y)) || element.contains(efp(rect.right, y))) { flag = true;休息; } } } if(csa) { // 另一种检查元素是否居中且大于视口的算法 if (flag == false) { var x = rect.left; var y = rect.top; // 从左上到右下 while(x < rect.right || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true;休息; } if(x < rect.right) { x++; } if(y < rect.bottom) { y++; } } if (flag == false) { x = rect.right; y = rect.top; // 从右上到左下 while(x > rect.left || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true;休息; } if(x > rect.left) { x--; } if(y < rect.bottom) { y++; } } } } } 返回标志; } // 检查多个元素的可见性 document.getElementById('container').addEventListener("scroll", function() { var elementList = document.getElementsByClassName("element"); var console = document.getElementById('console'); for (var i=0; i < elementList.length; i++) { // 我没有定义父级,所以它将是元素的父级 if (isVisible(elementList[i])) { console.innerHTML = "Element with id[ " + elementList[i].id + "] 是可见的!"; break; } else { console.innerHTML = "id [" + elementList[i].id + "] 的元素是隐藏的!"; } } }) ; // 动态添加元素 for(var i=4; i <= 6; i++) { var newElement = document.createElement("div"); newElement.id = "元素" + i; newElement.classList.add("元素"); document.getElementById('container').appendChild(newElement); } #console { 背景颜色:黄色; } #container { 宽度:300px;高度:100px;背景颜色:浅蓝色;溢出-y:自动;填充顶部:150px;边距:45px; } .element { 边距:400px;宽度:400px;高度:320px;背景颜色:绿色; } #element3 { 位置:相对;边距:40px;宽度:720px;高度:520px;背景颜色:绿色; } #element3::before { 内容:“”;位置:绝对;顶部:-10px;左:-10px;边距:0px;宽度:740px;高度:540px;边框:5px 虚线绿色;背景:透明; }
你看,当你在 element3 里面时,它无法判断它是否可见,因为我们只检查元素是否从侧面或角落可见。
其中包含 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) }; // 如果不在视口中,则返回 false if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) { return false ; } 变量标志 = 假; // 如果从左到右到达任何边界像素,则返回 true (var x = rect.left; x < rect.right; x++) { if (element.contains(efp(rect.top, x)) || element.contains (efp(rect.bottom, x))) { flag = true;休息; } } // 如果从上到下到达任何边框像素,则返回 true if (flag == false) { for (var y = rect.top; y < rect.bottom; y++) { if (element.contains(efp(rect.左, y)) || element.contains(efp(rect.right, y))) { flag = true;休息; } } } if(csa) { // 另一种检查元素是否居中且大于视口的算法 if (flag == false) { var x = rect.left; var y = rect.top; // 从左上到右下 while(x < rect.right || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true;休息; } if(x < rect.right) { x++; } if(y < rect.bottom) { y++; } } if (flag == false) { x = rect.right; y = rect.top; // 从右上到左下 while(x > rect.left || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true;休息; } if(x > rect.left) { x--; } if(y < rect.bottom) { y++; } } } } } 返回标志; } // 检查多个元素的可见性 document.getElementById('container').addEventListener("scroll", function() { var elementList = document.getElementsByClassName("element"); var console = document.getElementById('console'); for (var i=0; i < elementList.length; i++) { // 我没有定义父级,所以它将是元素的父级 // 它会执行 crossSearchAlgorithm if (isVisible(elementList[i],null,true)) { console.innerHTML = "id 为 [" + elementList[i].id + "] 的元素可见!"; break; } else { console.innerHTML = "id 为 [" + elementList[i].id + " 的元素] 被隐藏了!"; } } }); // 动态添加元素 for(var i=4; i <= 6; i++) { var newElement = document.createElement("div"); newElement.id = "元素" + i; newElement.classList.add("元素"); document.getElementById('container').appendChild(newElement); } #console { 背景颜色:黄色; } #container { 宽度:300px;高度:100px;背景颜色:浅蓝色;溢出-y:自动;填充顶部:150px;边距:45px; } .element { 边距:400px;宽度:400px;高度:320px;背景颜色:绿色; } #element3 { 位置:相对;边距:40px;宽度:720px;高度:520px;背景颜色:绿色; } #element3::before { 内容:“”;位置:绝对;顶部:-10px;左:-10px;边距:0px;宽度:740px;高度:540px;边框:5px 虚线绿色;背景:透明; }
要玩的 JSFiddle:http://jsfiddle.net/BerkerYuceer/grk5az2c/
如果元素的任何部分是否显示在视图中,则此代码用于提供更精确的信息。对于性能选项或仅垂直幻灯片,请勿使用此选项!此代码在绘制案例时更有效。
这是一个函数,它告诉元素是否在父元素的当前视口中可见:
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
);
}
更好的解决方案:
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;
}
我有同样的问题,并通过使用 getBoundingClientRect() 解决了这个问题。
这段代码是完全“通用的”,只需编写一次就可以工作(您不必为您想知道的每个元素都写出它在视口中)。
此代码仅检查它是否在视口中垂直,而不是水平。在这种情况下,变量(数组)'elements' 包含您正在检查的所有元素,以便在视口中垂直放置,因此在任何地方抓取您想要的任何元素并将它们存储在那里。
“for循环”循环遍历每个元素并检查它是否在视口中垂直。每次用户滚动时都会执行此代码!如果 getBoudingClientRect().top 小于视口的 3/4(元素在视口中的四分之一),它会注册为“在视口中”。
由于代码是通用的,您将想知道“哪个”元素在视口中。要找出它,您可以通过自定义属性、节点名称、id、类名称等来确定它。
这是我的代码(告诉我它是否不起作用;它已经在 Internet Explorer 11、Firefox 40.0.3、Chrome 版本 45.0.2454.85 m、Opera 31.0.1889.174 和 Windows 10 的 Edge 中进行了测试,[还没有 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.');
}
};
尽可能简单,IMO:
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
return Math.abs(coords.top) <= coords.height;
}
这将检查元素是否至少部分在视图中(垂直尺寸):
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}
}
这是对我有用的简单而小型的解决方案。
示例:您想查看该元素是否在具有溢出滚动的父元素中可见。
$(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');
}
})
这里的所有答案都是确定元素是否完全包含在视口中,而不仅仅是以某种方式可见。例如,如果在视图底部只能看到一半图像,则考虑到“外部”,此处的解决方案将失败。
我有一个用例,我通过 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));
这基本上是检查顶部或底部边界是否在视口中独立。另一端可能在外面,但只要一端在里面,它至少部分是“可见的”。
https://i.stack.imgur.com/E8ETHh.png
/**
* 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;
}
我使用这个函数(它只检查 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;
}
}
这是一个检查给定元素是否在其父元素中完全可见的片段:
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
);
}
Domysee 的答案 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;
}
对于类似的挑战,我非常喜欢 this gist,它为 scrollIntoViewIfNeeded() 公开了一个 polyfill。
回答所需的所有必要功夫都在此块内:
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
- 您应该了解一下...
不定期副业成功案例分享
return (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
可能更准确isElementInViewport(document.getElementById('elem'))
)而不是 jQuery 对象(例如,isElementInViewport($("#elem))
)。 jQuery 等效项是像这样添加[0]
:isElementInViewport($("#elem)[0])
。el is not defined