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

有没有一种有效的方法来判断DOM元素(在HTML文档中)当前是否可见(出现在视口中 )?

(关于Firefox的问题)


更新:时间流逝,所以我们的浏览器。 此技术不再推荐 ,如果您不需要支持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
  );
}

现在大多数浏览器都支持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); 

如果您关心网页上的选项卡是否处于活动状态并可见,则可以使用超棒的功能pageVisibiliy HTML5 API。

TODO:这种方法不能处理两种情况:

  • 使用z-index重叠
  • 在元素的容器中使用overflow-scroll

  • 尝试新的东西https://pawelgrzybek.com/the-intersection-observer-api-explained/


  • 更新

    在现代浏览器中,您可能需要查看Intersection Observer API,它具有以下优点:

  • 比听滚动事件更好的性能
  • 在跨域iframe中工作
  • 可以判断一个元素是否阻塞/相交
  • Intersection Observer正在成为一个完整的标准,并已在Chrome 51 +,Edge 15+和Firefox 55+中得到支持,并且正在为Safari开发。 还有一个polyfill可用。


    先前的回答

    Dan提供的答案存在一些问题,可能会使其成为某些情况下不适用的方法。 他在底部附近的回答中指出了其中的一些问题,他的代码会给出下列元素的误报:

  • 隐藏在被测试者面前的另一个元素
  • 在父母或祖先元素的可见区域之外
  • 通过使用CSS clip属性隐藏的元素或其子元素
  • 这些限制在以下简单测试结果中得到证明:

    失败的测试,使用isElementInViewport

    解决方案: 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/

    结果是:

    通过测试,使用isElementVisible

    补充笔记

    然而,这种方法并非没有其自身的局限性。 例如,即使前面的元素实际上并未隐藏其中的任何部分,在同一位置使用比其他元素更低的z索引进行测试的元素也将被标识为隐藏。 尽管如此,这种方法在Dan的解决方案没有涵盖的某些情况下有其用处。

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

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

    如果你想测试元素周围的更多点以获取可视性,即确保元素覆盖不超过50%,那么调整答案的最后部分不会花太多时间。 但是,请注意,如果您检查每个像素以确保其100%可见,则它可能会非常缓慢。

    链接地址: http://www.djcxy.com/p/14601.html

    上一篇: How to tell if a DOM element is visible in the current viewport?

    下一篇: SecItemAdd returning OSStatus code