如何使用JavaScript检查元素是否真正可见?

132

在JavaScript中,您如何检查元素是否实际可见?

我不仅指检查visibilitydisplay属性。我指的是,要检查元素不是:

  • visibility: hiddendisplay: none
  • 位于另一个元素下面
  • 已滚动到屏幕边缘之外

基于技术原因,我无法包含任何脚本。但是,由于Prototype已经在页面上,因此可以使用它。


新的HTML5“hidden”属性怎么样?(现代浏览器已经支持了)。<div hidden> ... </ div> - Konstantin Smolyanin
我在这里提出了关于overflow:hidden情况的解决方案: https://dev59.com/AI3da4cB1Zd3GeqPyVML - Yuval A.
我总是觉得关闭一个旧问题并将其作为新问题的重复问题是不好的。不幸的是,当时没有人关闭较新的那个问题,而它现在已经获得了稍微更多的浏览量和更受欢迎的答案。 - James Donnelly
@JamesDonnelly 请考虑重新开放这个问题。较新的问题的被接受答案无法解决这个问题。它们是不同的问题。(请参见我之前的评论。)谢谢! - jpaugh
新的版本与此版本不同 - 此版本还会询问有关检查遮挡元素的问题。 - Macha
显示剩余6条评论
15个回答

106

对于第二点。

我看到没有人建议使用document.elementFromPoint(x,y),在我看来这是测试一个元素是否被嵌套或被另一个元素隐藏的最快方法。您可以将目标元素的偏移量传递给该函数。

这是PPK关于elementFromPoint的测试页面:elementFromPoint

来自MDN文档

elementFromPoint()方法 - 可在Document和ShadowRoot对象上使用 - 返回指定坐标(相对于视口)的最顶层元素。


这不是只适用于IE的解决方案吗? - Bite code
@e-satis:在我的Firefox浏览器中可以运行,但在Opera浏览器中无法运行。 - Macha
6
元素的透明度呢?我想你可能会遇到这种情况:当elementFromPoint()方法显示一个元素被其它元素完全覆盖住了(你把它视为不可见),但用户却能看到它。请问如何解决? - Konstantin Smolyanin
@KonstantinSmolyanin,您询问的与OP不同,是要查看某个元素下方是否有其他元素(而不是上方是否有元素)。为此,您可以始终更改最顶部元素的CSS以使其具有“display:none”,然后再次检查相同区域。如果出现其他不是先前最顶部元素的父元素的内容,则表示下方有其他元素。 - Pluto
1
如果我们想要检查父元素的可见性,这种方法会失败。因此,在父元素的指定点,子元素将可用,而它是父元素的一部分。只需考虑包含多个嵌套元素的父<div>。实际上,父<div>是有效和可见的,但输出并未显示这一点。 - user172163
这是有道理的,但嵌套元素的情况下效果不佳。比如说,嵌套在锚点标签内的元素。返回的是锚点标签内的元素,而不是锚点标签本身。 - Questionnaire

42

我不知道这在老版本或不太现代的浏览器中有多少支持,但我正在使用类似于这样的东西(不需要任何库):

function visible(element) {
  if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
  var height = document.documentElement.clientHeight,
      rects = element.getClientRects(),
      on_top = function(r) {
        var x = (r.left + r.right)/2, y = (r.top + r.bottom)/2;
        return document.elementFromPoint(x, y) === element;
      };
  for (var i = 0, l = rects.length; i < l; i++) {
    var r = rects[i],
        in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height);
    if (in_viewport && on_top(r)) return true;
  }
  return false;
}

它检查元素是否具有面积大于0,然后检查元素的任何部分是否在视口内,并且它没有被另一个元素“遮盖”(实际上我仅在元素中心的一个单点上进行检查,因此无法保证100%——但如果您确实需要,您可以修改脚本以迭代元素的所有点……)。

更新

修改后的 on_top 函数检查每个像素:

on_top = function(r) {
  for (var x = Math.floor(r.left), x_max = Math.ceil(r.right); x <= x_max; x++)
  for (var y = Math.floor(r.top), y_max = Math.ceil(r.bottom); y <= y_max; y++) {
    if (document.elementFromPoint(x, y) === element) return true;
  }
  return false;
};

不清楚性能方面的情况 :)


1
元素的offsetWidth/offsetHeight是在寻找什么?它们在Chrome中似乎总是返回0。而document.documentElement.clientHeight获取的是元素的高度;难道不应该是document.body.clientHeight吗? - Seanonymous
对于窗口可见高度,这似乎可以在各种浏览器上都正常工作(甚至旧版IE):height = window.innerHeight?window.innerHeight:document.documentElement.clientHeight; - Seanonymous
1
如果一个元素被另一个元素完全覆盖(从这种方法的角度来看),但是覆盖的元素具有一定的透明度,那么下面的元素对用户可见,但在该方法中被视为不可见。 - Konstantin Smolyanin
原始的 on_top 函数对我来说不够一致,而修改后的性能损失太大了。但是其余部分非常优秀 :) - Florian Leitgeb
修改后的 on_top 函数对我非常有帮助...谢谢 @Tobias - Madhavan Sundararaj

7

谢谢。代码现在已经移动到:https://code.google.com/p/selenium/source/browse/javascript/selenium-core/scripts/selenium-api.js?r=789dad2290856eddeffea32789c9b1339ea60954 - Sali Hoo
1
代码已经再次移动:https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-core/scripts/selenium-api.js#L1885 - Tobias
这不是真的,Selenium 仅通过验证 css 的 'display' 和 'visibility' 属性来检查 isVisible,在你分享的链接 https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-core/scripts/selenium-api.js#L1885 中,Selenium.prototype.isVisible ... 返回 (visibility != "hidden" && _isDisplayed)。 - Liraz Shay
但是ChromeDriver会在元素实际上被隐藏在另一个元素下并且无法接收点击时抛出“ElementNotClickable at point”异常。但我认为其他浏览器不会抛出此异常(我确定Firefox不会检查)。 - Liraz Shay

4

有趣的问题。

这是我的方法。

  1. 首先检查element.style.visibility !== 'hidden' && element.style.display !== 'none'
  2. 然后使用document.elementFromPoint(element.offsetLeft, element.offsetTop)测试返回的元素是否是我预期的元素,如果一个元素完全重叠在另一个元素上,则很难检测到。
  3. 最后测试offsetTop和offsetLeft是否位于视口中,考虑滚动偏移量。

希望对你有所帮助。


你能详细解释一下 document.elementFromPoint 吗? - Macha
返回文档中从elementFromPoint方法调用的元素,该元素是位于给定点下方的最顶层元素。该点通过相对于包含文档的窗口或帧中最左上角的点的CSS像素坐标指定。 - Christophe Eblé

3

目前为止,这就是我做到的。它涵盖了1和3。但是,由于我不太熟悉Prototype(我更喜欢jQuery),所以我仍然在努力解决2。

function isVisible( elem ) {
    var $elem = $(elem);

    // First check if elem is hidden through css as this is not very costly:
    if ($elem.getStyle('display') == 'none' || $elem.getStyle('visibility') == 'hidden' ) {
        //elem is set through CSS stylesheet or inline to invisible
        return false;
    }

    //Now check for the elem being outside of the viewport
    var $elemOffset = $elem.viewportOffset();
    if ($elemOffset.left < 0 || $elemOffset.top < 0) {
        //elem is left of or above viewport
        return false;
    }
    var vp = document.viewport.getDimensions();
    if ($elemOffset.left > vp.width || $elemOffset.top > vp.height) {
        //elem is below or right of vp
        return false;
    }

    //Now check for elements positioned on top:
    //TODO: Build check for this using Prototype...
    //Neither of these was true, so the elem was visible:
    return true;
}

2
Prototype的元素库是在方法方面最强大的查询库之一。我建议您查看API。
一些提示:
  1. Checking visibility can be a pain, but you can use the Element.getStyle() method and Element.visible() methods combined into a custom function. With getStyle() you can check the actual computed style.

  2. I don't know exactly what you mean by "underneath" :) If you meant by it has a specific ancestor, for example, a wrapper div, you can use Element.up(cssRule):

    var child = $("myparagraph");
    if(!child.up("mywrapper")){
      // I lost my mom!
    }
    else {
      // I found my mom!
    }
    

    If you want to check the siblings of the child element you can do that too:

    var child = $("myparagraph");
    if(!child.previous("mywrapper")){
      // I lost my bro!
    } 
    else {
      // I found my bro!
    }
    
  3. Again, Element lib can help you if I understand correctly what you mean :) You can check the actual dimensions of the viewport and the offset of your element so you can calculate if your element is "off screen".

祝你好运!我在http://gist.github.com/117125上贴了一个prototypejs的测试用例。在你的情况下,我们根本不能信任getStyle()。为了最大化isMyElementReallyVisible函数的可靠性,你应该结合以下几点:
  • 检查计算样式(dojo有一个很好的实现,可以借鉴)
  • 检查视口偏移(原生方法)
  • 检查“下面”的z-index问题(在Internet Explorer下可能会有错误)

我觉得我完全误解了问题。你想确保你的元素在视口中可见,无论如何,对吗? - yaanno
1和3没问题。你误解了第2点。我有一些元素可以自由移动到屏幕上。如果用户将工具箱拖到其上方,那么该元素将位于另一个元素的下方。 - Macha
为了澄清我的上一个观点,工具箱将是主体内的一个div,而元素可能会深入几个级别。 - Macha

2
你可以使用clientHeight或clientWidth属性。
function isViewable(element){
  return (element.clientHeight > 0);
}

2
/**
 * Checks display and visibility of elements and it's parents
 * @param  DomElement  el
 * @param  boolean isDeep Watch parents? Default is true
 * @return {Boolean}
 *
 * @author Oleksandr Knyga <oleksandrknyga@gmail.com>
 */
function isVisible(el, isDeep) {
    var elIsVisible = true;

    if("undefined" === typeof isDeep) {
        isDeep = true;
    }

    elIsVisible = elIsVisible && el.offsetWidth > 0 && el.offsetHeight > 0;

    if(isDeep && elIsVisible) {

        while('BODY' != el.tagName && elIsVisible) {
            elIsVisible = elIsVisible && 'hidden' != window.getComputedStyle(el).visibility;
            el = el.parentElement;
        }
    }

    return elIsVisible;
}

1

1
尝试使用element.getBoundingClientRect(),它会返回一个带有以下属性的对象:
  • bottom
  • top
  • right
  • left
  • width - 浏览器相关
  • height - 浏览器相关
检查元素的 BoundingClientRect 的宽度和高度是否不为零,因为隐藏或非可见元素的值为零。如果这些值大于零,则该元素应该在页面中可见。然后检查 bottom 属性是否小于 screen.height,这意味着元素在视口内。(从技术上讲,还需要考虑浏览器窗口的顶部,包括搜索栏、按钮等。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接