检查元素在DOM中是否可见

Hom*_*ith 334 javascript dom

有什么方法可以检查一个元素是否在纯JS(没有jQuery)中可见?

因此,例如,在此页面中:Performance Bikes,如果您将鼠标悬停在Deals(在顶部菜单上),则会出现一个交易窗口,但在开始时它没有显示.它在HTML中,但不可见.

因此,给定一个DOM元素,我该如何检查它是否可见?我试过了:

window.getComputedStyle(my_element)['display']);
Run Code Online (Sandbox Code Playgroud)

但它似乎没有起作用.我想知道应该检查哪些属性.它出现在我的脑海里:

display !== 'none'
visibility !== 'hidden'
Run Code Online (Sandbox Code Playgroud)

还有其他我可能会失踪的人?

Ale*_*exZ 546

根据此MDN文档,元素的offsetParent属性将null在通过显示样式属性隐藏它或其任何父项时返回.只需确保元素不固定.如果您position: fixed;的页面上没有元素,则检查此脚本的脚本可能如下所示:

// Where el is the DOM element you'd like to test for visibility
function isHidden(el) {
    return (el.offsetParent === null)
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果你确实有固定的位置元素可能会在这个搜索中被捕获,你会遗憾地(并且慢慢地)使用它window.getComputedStyle().在这种情况下的功能可能是:

// Where el is the DOM element you'd like to test for visibility
function isHidden(el) {
    var style = window.getComputedStyle(el);
    return (style.display === 'none')
}
Run Code Online (Sandbox Code Playgroud)

选项#2可能稍微简单一些,因为它占据了更多的边缘情况,但我打赌它的速度也很慢,所以如果你不得不多次重复这个操作,最好避免它.

  • 还有FYI,刚刚发现`el.offsetParent`在IE9上没有用于非固定元素.或者看起来似乎无论如何.(不过对于IE11来说确实如此.)毕竟最后使用`getComputedStyle`. (6认同)
  • 只是对 window.getComputedStyle 方法的一个说明,它返回一个 _live_ CSSStyleDeclaration 对象。所以你只需要抓取一次样式,如果你需要多次执行检查,那么可以重新检查对象。 (4认同)
  • 唉!`getComputedStyle`无法正常工作:http://plnkr.co/edit/6CSCA2fe4Gqt4jCBP2wu?p=preview但是,[offsetParent`也是如此 - 也许应该使用两者的组合? (2认同)
  • 对于ie9 + ie10,您可以检查offsetParent = body是否为非可见元素. (2认同)
  • 我看到 `getComputedStyle(element).display` 在某些元素(例如 TABLE 元素)中具有 `table` 的值,其祖先是 `display:none` 使得本质上是无用的。 (2认同)

guy*_*abi 93

所有其他解决方案对我来说都打破了某些情况..

看到获胜的答案:

http://plnkr.co/edit/6CSCA2fe4Gqt4jCBP2wu?p=preview

最终,我认为最好的解决方案是$(elem).is(':visible')- 但是,这不是纯粹的javascript.这是jquery ..

所以我偷看了他们的来源并找到了我想要的东西

jQuery.expr.filters.visible = function( elem ) {
    return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};
Run Code Online (Sandbox Code Playgroud)

这是来源:https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js

  • @DougWilhelm 这就是显示源代码的原因。源代码是纯 JavaScript,因为 jQuery 在这个地方没有使用其他库。 (24认同)
  • 对于具有`visibility:hidden`的元素,这将返回`true` (8认同)
  • @YuvalA.:是的,因为元素仍然可见.将元素设置为`visibility:hidden`不再显示任何内容,但仍然采用元素的宽度和高度! (8认同)
  • 对于任何正在寻找也可以使用 `visibility:hidden` 的版本的人来说,添加 `&& window.getCompulatedStyle(elem).visibility !== "hidden"` 到此返回行的末尾似乎是可行的 (8认同)
  • OP要求一个没有jQuery的解决方案 (7认同)
  • @Michael你可以轻松浏览jQuery代码,如果你使用任何现代IDE(如果没有,请尝试使用),你可以在使用jQuery或任何其他lib时跳转到正确的代码部分.在浏览开源项目的代码库时,您可以学到很多东西. (3认同)

Mah*_*zad 50

Chrome 105(以及 Edge 和 Opera)和Firefox 106引入了如果元素可见则Element.checkVisibility()返回,否则返回。truefalse

该函数检查使元素不可见的各种因素,包括display:nonevisibilitycontent-visibilityopacity

let element = document.getElementById("myIcon");
let isVisible = element.checkVisibility({
    checkOpacity: true,      // Check CSS opacity property too
    checkVisibilityCSS: true // Check CSS visibility property too
});
Run Code Online (Sandbox Code Playgroud)

旁注checkVisibility()以前称为isVisible(). 请参阅此 GitHub 问题
请参阅checkVisibility()此处的规范草案

  • 真正有效的优雅解决方案。尽管浏览器支持非常有限,但还是推荐的解决方案。https://caniuse.com/mdn-api_element_checkvisibility (3认同)
  • @stackers,您对“有限”的定义将取决于您的用户群,而不是浏览器支持。对于任何 iOS 流量较高的网站,“Safari 不支持”也是“非常有限的支持”。 (3认同)
  • 应该注意的是,即使是现代(16.3)版本的 Safari 也不支持 `checkVisibility`。 (2认同)

Oha*_*von 45

如果您对用户可见:

function isVisible(elem) {
    if (!(elem instanceof Element)) throw Error('DomUtil: elem is not an element.');
    const style = getComputedStyle(elem);
    if (style.display === 'none') return false;
    if (style.visibility !== 'visible') return false;
    if (style.opacity < 0.1) return false;
    if (elem.offsetWidth + elem.offsetHeight + elem.getBoundingClientRect().height +
        elem.getBoundingClientRect().width === 0) {
        return false;
    }
    const elemCenter   = {
        x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
        y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
    };
    if (elemCenter.x < 0) return false;
    if (elemCenter.x > (document.documentElement.clientWidth || window.innerWidth)) return false;
    if (elemCenter.y < 0) return false;
    if (elemCenter.y > (document.documentElement.clientHeight || window.innerHeight)) return false;
    let pointContainer = document.elementFromPoint(elemCenter.x, elemCenter.y);
    do {
        if (pointContainer === elem) return true;
    } while (pointContainer = pointContainer.parentNode);
    return false;
}
Run Code Online (Sandbox Code Playgroud)

测试(使用mocha术语):

describe.only('visibility', function () {
    let div, visible, notVisible, inViewport, leftOfViewport, rightOfViewport, aboveViewport,
        belowViewport, notDisplayed, zeroOpacity, zIndex1, zIndex2;
    before(() => {
        div = document.createElement('div');
        document.querySelector('body').appendChild(div);
        div.appendChild(visible = document.createElement('div'));
        visible.style       = 'border: 1px solid black; margin: 5px; display: inline-block;';
        visible.textContent = 'visible';
        div.appendChild(inViewport = visible.cloneNode(false));
        inViewport.textContent = 'inViewport';
        div.appendChild(notDisplayed = visible.cloneNode(false));
        notDisplayed.style.display = 'none';
        notDisplayed.textContent   = 'notDisplayed';
        div.appendChild(notVisible = visible.cloneNode(false));
        notVisible.style.visibility = 'hidden';
        notVisible.textContent      = 'notVisible';
        div.appendChild(leftOfViewport = visible.cloneNode(false));
        leftOfViewport.style.position = 'absolute';
        leftOfViewport.style.right = '100000px';
        leftOfViewport.textContent = 'leftOfViewport';
        div.appendChild(rightOfViewport = leftOfViewport.cloneNode(false));
        rightOfViewport.style.right       = '0';
        rightOfViewport.style.left       = '100000px';
        rightOfViewport.textContent = 'rightOfViewport';
        div.appendChild(aboveViewport = leftOfViewport.cloneNode(false));
        aboveViewport.style.right       = '0';
        aboveViewport.style.bottom       = '100000px';
        aboveViewport.textContent = 'aboveViewport';
        div.appendChild(belowViewport = leftOfViewport.cloneNode(false));
        belowViewport.style.right       = '0';
        belowViewport.style.top       = '100000px';
        belowViewport.textContent = 'belowViewport';
        div.appendChild(zeroOpacity = visible.cloneNode(false));
        zeroOpacity.textContent   = 'zeroOpacity';
        zeroOpacity.style.opacity = '0';
        div.appendChild(zIndex1 = visible.cloneNode(false));
        zIndex1.textContent = 'zIndex1';
        zIndex1.style.position = 'absolute';
        zIndex1.style.left = zIndex1.style.top = zIndex1.style.width = zIndex1.style.height = '100px';
        zIndex1.style.zIndex = '1';
        div.appendChild(zIndex2 = zIndex1.cloneNode(false));
        zIndex2.textContent = 'zIndex2';
        zIndex2.style.left = zIndex2.style.top = '90px';
        zIndex2.style.width = zIndex2.style.height = '120px';
        zIndex2.style.backgroundColor = 'red';
        zIndex2.style.zIndex = '2';
    });
    after(() => {
        div.parentNode.removeChild(div);
    });
    it('isVisible = true', () => {
        expect(isVisible(div)).to.be.true;
        expect(isVisible(visible)).to.be.true;
        expect(isVisible(inViewport)).to.be.true;
        expect(isVisible(zIndex2)).to.be.true;
    });
    it('isVisible = false', () => {
        expect(isVisible(notDisplayed)).to.be.false;
        expect(isVisible(notVisible)).to.be.false;
        expect(isVisible(document.createElement('div'))).to.be.false;
        expect(isVisible(zIndex1)).to.be.false;
        expect(isVisible(zeroOpacity)).to.be.false;
        expect(isVisible(leftOfViewport)).to.be.false;
        expect(isVisible(rightOfViewport)).to.be.false;
        expect(isVisible(aboveViewport)).to.be.false;
        expect(isVisible(belowViewport)).to.be.false;
    });
});
Run Code Online (Sandbox Code Playgroud)

  • 但是不适用于嵌套滚动。 (3认同)

Ash*_*nto 36

这可能有所帮助: 通过将元素放置在最左边的位置来隐藏元素,然后检查offsetLeft属性.如果你想使用jQuery,你可以简单地检查:visible选择器并获得元素的可见性状态.

HTML:

<div id="myDiv">Hello</div>
Run Code Online (Sandbox Code Playgroud)

CSS:

<!-- for javaScript-->
#myDiv{
   position:absolute;
   left : -2000px;
}

<!-- for jQuery -->
#myDiv{
    visibility:hidden;
}
Run Code Online (Sandbox Code Playgroud)

javaScript:

var myStyle = document.getElementById("myDiv").offsetLeft;

if(myStyle < 0){
     alert("Div is hidden!!");
}
Run Code Online (Sandbox Code Playgroud)

jQuery:

if(  $("#MyElement").is(":visible") == true )
{  
     alert("Div is hidden!!");        
}
Run Code Online (Sandbox Code Playgroud)

的jsfiddle

  • OP请求一个no-jQuery答案. (8认同)
  • 对于jQuery示例,警报不应该说"Div是可见的吗?" (6认同)
  • @StephenQuan,我已经用jQuery和javaScript解决方案更新了答案。 (2认同)

Mac*_*cio 34

2021年解决方案

根据MDN 文档,交互观察者异步观察目标元素与祖先元素或顶级文档视口的交集的变化。这意味着每次元素与视口相交时,交互观察者都会触发。

截至 2021 年,当前除 IE 之外的所有浏览器都支持交集观察器。

执行

const el = document.getElementById("your-target-element");
const observer = new IntersectionObserver((entries) => {
    if(entries[0].isIntersecting){
         // el is visible
    } else {
         // el is not visible
    }
});

observer.observe(el); // Asynchronous call
Run Code Online (Sandbox Code Playgroud)

该处理程序将在最初创建时触发。然后每当它变得稍微可见或完全可见时它就会触发。当某个元素在视口中实际不可见时,该元素被视为不可见。因此,如果向下滚动并且元素离开屏幕,则观察者将触发并且// el is not visible代码将被触发 - 即使元素仍然“显示”(即没有display:noneor visibility:hidden)。重要的是元素中是否有任何像素在视口中实际可见

  • 重要提示:IntersectionObserver 回调是异步的 =&gt; 调用 `observer.observe(el)` 后不会立即得到结果; (2认同)

Yva*_*van 29

使用与jQuery相同的代码:

jQuery.expr.pseudos.visible = function( elem ) {
    return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};
Run Code Online (Sandbox Code Playgroud)

所以,在一个函数中:

function isVisible(e) {
    return !!( e.offsetWidth || e.offsetHeight || e.getClientRects().length );
}
Run Code Online (Sandbox Code Playgroud)

就像我的Win/IE10,Linux/Firefox.45,Linux/Chrome.52中的魅力......

非常感谢没有jQuery的jQuery!

  • 很好,但是为什么!!(双重否定)? (4认同)
  • 不错,但不包括被溢出隐藏的元素。 (3认同)
  • 将结果强制为布尔值。由于e.offsetWidth是一个整数,如果e.offsetWidth大于零(元素可见),则e!offsetWidth将返回false。因此,如果`e.offsetWidth`大于零,则在`!! e.offsetWidth`中添加另一个`!`将返回true。这是`return e.offsetWidth&gt; 0吗?true:false或明显地返回return e.offsetWidth&gt; 0。 (3认同)
  • @Jarad 你是对的:-) 双 `!!` 用于强制结果为布尔值。但如果你想要相反的结果,只需要一个。 (3认同)

eli*_*z82 24

接受的答案对我不起作用。

2020 年细分。

  1. (elem.offsetParent !== null)方法在 Firefox 中工作正常,但在 Chrome 中无效。在 Chrome 中,如果在页面中可见,即使元素position: fixed也会offsetParent返回null

    用户 Phrogz 对具有不同属性的元素进行了大型测试(2,304 个 div)以演示该问题。/sf/answers/814776511/使用多个浏览器运行它以查看差异。

    演示:

    //different results in Chrome and Firefox
    console.log(document.querySelector('#hidden1').offsetParent); //null Chrome & Firefox
    console.log(document.querySelector('#fixed1').offsetParent); //null in Chrome, not null in Firefox
    Run Code Online (Sandbox Code Playgroud)
        <div id="hidden1" style="display:none;"></div>
        <div id="fixed1" style="position:fixed;"></div>
    Run Code Online (Sandbox Code Playgroud)

  2. (getComputedStyle(elem).display !== 'none')不起作用,因为该元素可以是无形的,因为父母中的一方显示属性设置为无,getComputedStyle将不会赶上。

    演示:

    var child1 = document.querySelector('#child1');
    console.log(getComputedStyle(child1).display);
    //child will show "block" instead of "none"
    Run Code Online (Sandbox Code Playgroud)
    <div id="parent1" style="display:none;">
      <div id="child1" style="display:block"></div>
    </div>
    Run Code Online (Sandbox Code Playgroud)

  3. (elem.clientHeight !== 0)。此方法不受position: fixed并检查元素父项是否不可见。但是它在没有 css 布局的简单元素方面存在问题,请在此处查看更多信息

    演示:

    console.log(document.querySelector('#div1').clientHeight); //not zero
    console.log(document.querySelector('#span1').clientHeight); //zero
    Run Code Online (Sandbox Code Playgroud)
    <div id="div1">test1 div</div>
    <span id="span1">test2 span</span>
    Run Code Online (Sandbox Code Playgroud)

  4. (elem.getClientRects().length !== 0)可能似乎解决了以前的3种方法的问题。但是,使用 CSS 技巧(除此之外display: none)隐藏在页面中的元素存在问题。

    演示

    console.log(document.querySelector('#notvisible1').getClientRects().length);
    console.log(document.querySelector('#notvisible1').clientHeight);
    console.log(document.querySelector('#notvisible2').getClientRects().length);
    console.log(document.querySelector('#notvisible2').clientHeight);
    console.log(document.querySelector('#notvisible3').getClientRects().length);
    console.log(document.querySelector('#notvisible3').clientHeight);
    Run Code Online (Sandbox Code Playgroud)
    <div id="notvisible1" style="height:0; overflow:hidden; background-color:red;">not visible 1</div>
    
    <div id="notvisible2" style="visibility:hidden; background-color:yellow;">not visible 2</div>
    
    <div id="notvisible3" style="opacity:0; background-color:blue;">not visible 3</div>
    Run Code Online (Sandbox Code Playgroud)

结论。

所以我向你展示的是,没有一种方法是完美的。要进行适当的可见性检查,您必须结合使用最后 3 种方法。


Mat*_*hew 14

结合以上几个答案:

function isVisible (ele) {
    var style = window.getComputedStyle(ele);
    return  style.width !== "0" &&
    style.height !== "0" &&
    style.opacity !== "0" &&
    style.display!=='none' &&
    style.visibility!== 'hidden';
}
Run Code Online (Sandbox Code Playgroud)

就像AlexZ所说的那样,如果你更具体地了解你正在寻找什么,这可能比你的其他一些选择要慢,但这应该抓住隐藏元素的所有主要方式.

但是,它还取决于对您而言可见的重要因素.例如,div的高度可以设置为0px,但内容仍然可见,具体取决于溢出属性.或者div的内容可以与背景颜色相同,因此用户看不到它,但仍然在页面上呈现.或者div可以移出屏幕或隐藏在其他div之后,或者它的内容可能是不可见的但边框仍然可见.在某种程度上,"可见"是一个主观的术语.


Vla*_*ada 7

如果element是常规可见的(display:block和visibillity:visible),但是某些父容器是隐藏的,那么我们可以使用clientWidthclientHeight进行检查.

function isVisible (ele) {
  return  ele.clientWidth !== 0 &&
    ele.clientHeight !== 0 &&
    ele.style.opacity !== 0 &&
    ele.style.visibility !== 'hidden';
}
Run Code Online (Sandbox Code Playgroud)

Plunker(点击这里)


Mun*_*war 7

AlexZ的getComputedStyle()解决方案相比,我有一个更高效的解决方案,当一个人拥有位置'固定'元素时,如果有人愿意忽略一些边缘情况(检查注释):

function isVisible(el) {
    /* offsetParent would be null if display 'none' is set.
       However Chrome, IE and MS Edge returns offsetParent as null for elements
       with CSS position 'fixed'. So check whether the dimensions are zero.

       This check would be inaccurate if position is 'fixed' AND dimensions were
       intentionally set to zero. But..it is good enough for most cases.*/
    if (!el.offsetParent && el.offsetWidth === 0 && el.offsetHeight === 0) {
        return false;
    }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

旁注:严格来说,需要首先定义"可见性".在我的情况下,我正在考虑一个元素可见,只要我可以运行所有DOM方法/属性没有问题(即使不透明度为0或CSS可见性属性是'隐藏'等).


inc*_*ate 6

仅供参考,应注意的是getBoundingClientRect()在某些情况下可以工作。

例如,使用简单的检查元素是否隐藏的方法display: none可能如下所示:

var box = element.getBoundingClientRect();
var visible = box.width && box.height;
Run Code Online (Sandbox Code Playgroud)

这也很方便,因为它还涵盖了零宽度、零高度和position: fixed大小写。但是,它不应报告用opacity: 0或隐藏的元素visibility: hidden(但两者都不会offsetParent)。


Tok*_*mon 6

所以我发现的是最可行的方法:

function visible(elm) {
  if(!elm.offsetHeight && !elm.offsetWidth) { return false; }
  if(getComputedStyle(elm).visibility === 'hidden') { return false; }
  return true;
}
Run Code Online (Sandbox Code Playgroud)

这是基于以下事实:

  • 一个display: none元素(甚至是嵌套的)没有宽度和高度。
  • visiblityhidden即使对于嵌套元素。

所以不需要offsetParent在 DOM 树中测试或循环来测试哪个父级有visibility: hidden. 即使在 IE 9 中,这也应该有效。

您可能会争论opacity: 0折叠元素(有宽度但没有高度 - 反之亦然)是否真的不可见。但话又说回来,他们不是每说隐藏。


XML*_*XML 5

如果我们只是收集检测能见度的基本方法,那么我不要忘记:

opacity > 0.01; // probably more like .1 to actually be visible, but YMMV
Run Code Online (Sandbox Code Playgroud)

至于如何获取属性:

element.getAttribute(attributename);
Run Code Online (Sandbox Code Playgroud)

所以,在你的例子中:

document.getElementById('snDealsPanel').getAttribute('visibility');
Run Code Online (Sandbox Code Playgroud)

但是,哇?它在这里不起作用.仔细观察,您会发现可见性不是作为元素的属性更新,而是使用style属性.这是尝试做你正在做的事情的许多问题之一.其中:你无法保证在元素中实际可以看到的东西,只是因为它的可见性,显示和不透明度都具有正确的值.它仍然可能缺乏内容,或者可能缺少高度和宽度.另一个对象可能会模糊它.有关更多详细信息,快速Google搜索会显示这一点,甚至还包括一个试图解决问题的库.(YMMV)

查看以下内容,这些内容可能与此问题重复,并提供了出色的答案,包括强大的John Resig的一些见解.但是,您的特定用例与标准用例略有不同,因此我将避免标记:

(编辑:OP说他是划伤页面,不创建他们,所以不适用)更好的选择?将元素的可见性绑定到模型属性,并始终使可视化取决于该模型,就像Angular对ng-show所做的那样.你可以使用你想要的任何工具来做到这一点:Angular,普通的JS,等等.更好的是,您可以随着时间的推移更改DOM实现,但您始终能够从模型而不是DOM中读取状态.从DOM中读出你的真相很糟糕.而且很慢.更好的方法是检查模型,并相信您的实现以确保DOM状态反映模型.(并使用自动化测试来确认该假设.)


Guy*_*ika 5

ohad navon 的回答的一点补充。

如果元素的中心属于另一个元素,我们将找不到它。

所以要确保发现元素的其中一个点是可见的

function isElementVisible(elem) {
    if (!(elem instanceof Element)) throw Error('DomUtil: elem is not an element.');
    const style = getComputedStyle(elem);
    if (style.display === 'none') return false;
    if (style.visibility !== 'visible') return false;
    if (style.opacity === 0) return false;
    if (elem.offsetWidth + elem.offsetHeight + elem.getBoundingClientRect().height +
        elem.getBoundingClientRect().width === 0) {
        return false;
    }
    var elementPoints = {
        'center': {
            x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
            y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
        },
        'top-left': {
            x: elem.getBoundingClientRect().left,
            y: elem.getBoundingClientRect().top
        },
        'top-right': {
            x: elem.getBoundingClientRect().right,
            y: elem.getBoundingClientRect().top
        },
        'bottom-left': {
            x: elem.getBoundingClientRect().left,
            y: elem.getBoundingClientRect().bottom
        },
        'bottom-right': {
            x: elem.getBoundingClientRect().right,
            y: elem.getBoundingClientRect().bottom
        }
    }

    for(index in elementPoints) {
        var point = elementPoints[index];
        if (point.x < 0) return false;
        if (point.x > (document.documentElement.clientWidth || window.innerWidth)) return false;
        if (point.y < 0) return false;
        if (point.y > (document.documentElement.clientHeight || window.innerHeight)) return false;
        let pointContainer = document.elementFromPoint(point.x, point.y);
        if (pointContainer !== null) {
            do {
                if (pointContainer === elem) return true;
            } while (pointContainer = pointContainer.parentNode);
        }
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)