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

别说谁变了你拦得住时间么 提交于 2019-12-16 18:50:37

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

(问题是指Firefox)


#1楼

请参阅使用getBoundingClientRect边缘源。 就像是:

function inViewport (el) {

    var r, html;
    if ( !el || 1 !== el.nodeType ) { return false; }
    html = document.documentElement;
    r = el.getBoundingClientRect();

    return ( !!r 
      && r.bottom >= 0 
      && r.right >= 0 
      && r.top <= html.clientHeight 
      && r.left <= html.clientWidth 
    );

}

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


#2楼

更新:时间在前进,我们的浏览器也在前进。 不再推荐使用此技术,如果不需要支持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
  );
}

#3楼

更好的解决方案:

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

#4楼

更新资料

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

  • 比收听滚动事件更好的性能
  • 适用于跨网域iframe
  • 可以判断一个元素是否正在阻碍/相交

Intersection Observer正在成为完善的标准,并且已在Chrome 51 +,Edge 15+和Firefox 55+中得到支持,并且正在为Safari开发。 还有一个polyfill可用。


先前的答案

Dan提供答案存在一些问题,可能使它不适用于某些情况。 他在底部的答案中指出了其中一些问题,即他的代码将对以下元素产生误报:

  • 被正在测试的元素前面的另一个元素隐藏
  • 在父元素或祖先元素的可见区域之外
  • 使用CSS 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%可见,这可能会非常慢。


#5楼

我尝试了Dan的答案, 但是用于确定范围的代数意味着该元素必须既≤视口大小,又必须完全在视口内才能得到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() */;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!