现在大多数浏览器都支持getBoundingClientRect方法,这已成为最佳实践。使用旧的答案非常慢 , 不准确,并有几个错误 。
选择正确的解决方案几乎从不精确 。您可以阅读有关其错误的更多信息。
该解决方案在 IE7 +,iOS5 + Safari,Android2 +,Blackberry,Opera Mobile 和 IE Mobile 10 上进行了测试 。
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); // IE9+ :(
attachEvent('onload', handler);
attachEvent('onscroll', handler);
attachEvent('onresize', handler);
}
*/
如果您进行任何 DOM 修改,它们可以改变元素的可见性。
准则和常见陷阱:
也许你需要跟踪页面缩放 / 移动设备捏? jQuery 应该处理缩放 / 捏交叉浏览器,否则第一个或第二个链接应该可以帮助你。
如果修改 DOM ,则会影响元素的可见性。您应该控制它并手动调用handler()
。不幸的是,我们没有跨浏览器onrepaint
事件。另一方面,它允许我们进行优化并仅对可以改变元素可见性的 DOM 修改执行重新检查。
从来没有在 jQuery $(document).ready()中使用它,因为此刻没有保证 CSS 。您的代码可以在硬盘驱动器上与您的 CSS 本地工作,但一旦放在远程服务器上它将失败。
触发DOMContentLoaded
后, 将应用样式 ,但尚未加载图像 。所以,我们应该添加window.onload
事件监听器。
我们还无法捕捉缩放 / 捏合事件。
最后的手段可能是以下代码:
/* TODO: this looks like a very bad code */
setInterval(handler, 600);
如果您关心网页的标签是否有效且可见,您可以使用精彩的功能页面 Visibiliy HTML5 API。
TODO:此方法不处理两种情况:
z-index
重叠在元素的容器中使用overflow-scroll
尝试新的东西https://pawelgrzybek.com/the-intersection-observer-api-explained/
更新:时间推进,我们的浏览器也是如此。如果您不需要支持 IE <7, 则不再推荐使用此技术 ,您应该使用 @ Dan 的解决方案( https://stackoverflow.com/a/7557433/5628 )。
原始解决方案(现已过时):
这将检查元素在当前视口中是否完全可见:
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
);
}
在现代浏览器中,您可能需要查看Intersection Observer API ,它提供以下好处:
Intersection Observer 正在成为一个成熟的标准,并已在 Chrome 51 +,Edge 15 + 和 Firefox 55 + 中得到支持,目前正在开发 Safari。还有一个polyfill可用。
Dan 提供的答案存在一些问题,可能会使其在某些情况下成为不合适的方法。其中一些问题在他的答案附近指出,他的代码将给出以下元素的误报:
clip
属性隐藏的元素或其子元素这些限制在以下简单测试结果中得到证明:
isElementVisible()
以下是这些问题的解决方案,下面是测试结果,并解释了代码的某些部分。
function isElementVisible(el) {
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || doc.documentElement.clientWidth,
vHeight = window.innerHeight || doc.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/
结果如下:
然而,这种方法并非没有自身的局限性。例如,即使前面的元素实际上没有隐藏它的任何部分,使用比同一位置的另一个元素更低的 z-index 测试的元素也将被识别为隐藏。尽管如此,这种方法在某些情况下仍然使用 Dan 的解决方案。
element.getBoundingClientRect()
和document.elementFromPoint()
都是 CSSOM 工作草案规范的一部分,并且至少在 IE 6 及更高版本和大多数桌面浏览器中都支持很长时间(尽管不是很完美)。有关更多信息,请参阅这些函数的 Quirksmode 。
contains()
用于查看document.elementFromPoint()
返回的元素是否是我们正在测试可见性的元素的子节点。如果返回的元素是相同的元素,它也返回 true。这只会使检查更加健壮。所有主流浏览器都支持它,Firefox 9.0 是最后一个添加它的人。对于较早的 Firefox 支持,请查看此答案的历史记录。
如果你想在元素周围测试更多的点以获得可见性 - 也就是说,为了确保元素不被覆盖超过 50%,那么调整答案的最后部分并不需要太多。但是,请注意,如果检查每个像素以确保它是 100%可见,则可能会非常慢。