检查元素在DOM中是否可见

602

有没有纯JS(不用jQuery)可以检查元素是否可见的方法?

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

window.getComputedStyle(my_element)['display']);

但它似乎没有起作用。我想知道我应该检查哪些属性。以下一些可能值得检查:

display !== 'none'
visibility !== 'hidden'

还有其他我可能遗漏的吗?


3
它不使用显示,而是使用可见性,因此检查其可见性(隐藏或可见)。例如:document.getElementById('snDealsPanel').style.visibility - PSL
PSL。如果我想要更通用,我应该检查哪些属性:可见性、显示...? - Hommer Smith
你可以用自己的方式来泛化它,但我的意思是它使用可见性检查元素。 - PSL
这是我针对这个问题的代码(没有使用jQuery):https://dev59.com/SnVC5IYBdhLWcg3wz0l9#22969337 - Aleko
0 < document.querySelector('foo').getBoundingClientRect().height0 < document.querySelector('foo').getBoundingClientRect().height - Time Killer
30个回答

929
根据这份 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)
}

另一方面,如果您有可能被捕捉到的固定位置元素,则不幸的是(而且速度会很慢),您将需要使用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')
}

选项#2可能会更加直接,因为它涵盖了更多的边缘情况,但我敢打赌它也会慢得多,所以如果您需要重复执行此操作多次,最好还是避免使用它。


7
顺便提一下,我刚发现el.offsetParent在IE9中对于非fixed元素无法正常工作。不过好像是这样的。(在IE11上没有问题。)所以最终还是用了getComputedStyle - Nick
3
哎呀!getComputedStyle 的工作不正确:http://plnkr.co/edit/6CSCA2fe4Gqt4jCBP2wu?p=preview。 然而,offsetParent 也是如此-也许应该结合使用这两个方法? - guy mograbi
2
对于IE9和IE10,您可以检查offsetParent是否等于body来检查非可见元素。 - SuperUberDuper
2
我看到在一些元素中(例如表格元素),其祖先元素为 display:none 时,getComputedStyle(element).display 的值为 table,使其本质上无用。 - Michael
9
关于window.getComputedStyle方法的说明,它返回一个实时的CSSStyleDeclaration对象。因此,您只需要获取一次样式,然后如果必须多次执行检查,则可以重新检查该对象。 - abenbot
显示剩余16条评论

168

其他所有的解决方案在某些情况下都出现了问题...

请查看获胜答案失败的情况:

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

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


16
对于具有visibility:hidden属性的元素,此代码将返回true - Yuval A.
19
是的,因为元素仍然可见。将元素设置为visibility:hidden会使内容不再显示,但元素仍然占据宽度和高度! - Jacob van Lingen
7
OP要求没有jQuery的解决方案。 - Doug Wilhelm
8
若想要适用于 visibility:hidden 的版本,可以在此返回行末尾添加 && window.getComputedStyle(elem).visibility !== "hidden"。这样做似乎可行。 - dkniffin
21
这就是为什么会显示源代码。该源代码是纯JavaScript编写的,因为jQuery在此处未使用其他库。 - Binarus
1
对我来说,一个更好的解决方案是在条件中使用&&,例如(elem.offsetWidth && elem.offsetHeight && elem.getClientRects().length),因为如果宽度或高度为0,那么元素对用户来说是不可见的。 - lanxion

109

如果您希望用户可见:

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

在(使用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;
    });
});

如果元素位于视口之外,则可以通过检查第一个pointContainer的“if(!pointContainer)return false;”来捕获边缘情况。 - Jerry Deng
1
如果您想检查用户是否可能看到它,您将不得不使用 scrollIntoView,对吧?这相当耗费资源。有没有其他聪明的方法? - Kim Kern
如果重叠元素(准确地说:所有重叠元素)的不透明度小于1,该怎么办? - Shimon S

58

使用和jQuery相同的代码:

the same code as jQuery does:
jQuery.expr.pseudos.visible = function( elem ) {
    return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};

所以,在一个函数中:

function isVisible(e) {
    return !!( e.offsetWidth || e.offsetHeight || e.getClientRects().length );
}

在我的Win/IE10、Linux/Firefox.45和Linux/Chrome.52上非常顺畅地运行,感谢jQuery(没有用到jQuery)!


3
不错,但是没有涵盖被溢出隐藏的元素。 - Slava
4
很好,但是为什么会有双重否定的感觉? - Sunil Garg
11
强制将结果转换为布尔值。由于e.offsetWidth是整数,如果e.offsetWidth大于零(元素可见),!e.offsetWidth将返回false。因此,在!后添加另一个!,即!!e.offsetWidth,如果e.offsetWidth大于零,则将返回true。这是return e.offsetWidth > 0 ? true : false或者显然的return e.offsetWidth > 0的简写。 - Yvan
1
如果我想要一个“isHidden”函数,我可以使用单个!而不是!!吗? - Jarad
3
@Jarad 你说得对 :-) 双重 !! 用于强制结果为布尔值。但是由于你想要相反的结果,只需要一个就可以了。 - Yvan
为什么是50票?这是八个月前Guy Mograbi的回答的副本。 - gknicker

45

对我来说,被接受的答案没有起作用。

2020年度综述。

  1. The (elem.offsetParent !== null) method works fine in Firefox but not in Chrome. In Chrome position: fixed will also make offsetParent return null even the element if visible in the page.

    User Phrogz conducted a large test (2,304 divs) on elements with varying properties to demonstrate the issue. https://dev59.com/DXVC5IYBdhLWcg3wZwTj#11639664 . Run it with multiple browsers to see the differences.

    Demo:

    //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
        <div id="hidden1" style="display:none;"></div>
        <div id="fixed1" style="position:fixed;"></div>

  2. The (getComputedStyle(elem).display !== 'none') does not work because the element can be invisible because one of the parents display property is set to none, getComputedStyle will not catch that.

    Demo:

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

  3. The (elem.clientHeight !== 0). This method is not influenced by position: fixed and it also check if element parents are not-visible. But it has problems with simple elements that do not have a css layout and inline elements, see more here

    Demo:

    console.log(document.querySelector('#inline1').clientHeight); //zero
    console.log(document.querySelector('#div1').clientHeight); //not zero
    console.log(document.querySelector('#span1').clientHeight); //zero
    <div id="inline1" style="display:inline">test1 inline</div>
    <div id="div1">test2 div</div>
    <span id="span1">test3 span</span>

  4. The (elem.getClientRects().length !== 0) may seem to solve the problems of the previous 3 methods. However it has problems with elements that use CSS tricks (other then display: none) to hide in the page.

    Demo

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

结论。

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


在Chrome 88.0.4324.104中,第一个结果给了我null,null。 - Alessandro_Russo
我用你的解决方案添加了一个JS代码片段,更新了你的出色答案! - Melloware
父元素被隐藏时无法工作。 - Byzod
@Byzod 这个函数是由管理员添加的。为了避免我的答案被踩,我会将其删除。正如我在答案中所说的,方法4对于大多数情况来说已经足够了。仅当您想要检查CSS可见性技巧以隐藏页面元素时,才应使用方法2和3(在方法4中,我已经给出了3个不可见性CSS技巧的示例)。例如,可以使用getComputedStyle来检查opacity>0。 - crisc2000
@Byzod我已经用一个函数做了一个例子https://jsfiddle.net/79uzoj4v/。其他人:请不要编辑我的答案以添加功能。回答的范围是对之前讨论过的解决方案进行分解。 - crisc2000

44

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

<div id="myDiv">Hello</div>

CSS:

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

<!-- for jQuery -->
#myDiv{
    visibility:hidden;
}

JavaScript:

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

if(myStyle < 0){
     alert("Div is hidden!!");
}

jQuery :

if(  $("#MyElement").is(":visible") == true )
{  
     alert("Div is visible!!");        
}

jsFiddle


25
楼主要求不使用jQuery进行操作。 - Stephen Quan
我猜它后来被编辑了。当我回答时,这个帖子中没有提到过。 - Md Ashaduzzaman
3
@StephenQuan,我已更新答案,提供了jQuery和JavaScript的解决方案。 - Md Ashaduzzaman
6
对于 jQuery 的示例,警告框应该显示“Div 是可见的”,是这样吗? - Andrei Bazanov
1
我很高兴尽管没有要求使用jQuery,但仍有多种解决方案。未来像我一样有或没有使用jQuery的读者也可以在这里得到答案并进行选择:D - Joe DF
显示剩余3条评论

42

Chrome 105(以及Edge和Opera)和Firefox 106引入了Element.checkVisibility()函数,它会返回true当元素可见时,并在不可见时返回false

该函数检查许多因素,这些因素会使元素变得不可见,包括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
});

旁注: checkVisibility() 之前被称为 isVisible()。请参见此 GitHub 问题
请在checkVisibility() 规范草案中查看


3
优雅的解决方案,实际上是可行的。虽然浏览器支持非常有限,但这是一个推荐的解决方案。https://caniuse.com/mdn-api_element_checkvisibility - abhijithvijayan
2
需要注意的是,即使是现代(16.3)版本的Safari也不支持checkVisibility - amphetamachine
截至2023年3月初,似乎还没有polyfill。 - 13twelve
1
我不会把“除了Safari之外的一切”称作“非常有限”。 - stackers
3
@stackers,你对“有限”的定义将取决于你的用户群,而不是浏览器支持。对于任何 iOS 流量较高的站点,“在 Safari 中不支持”也意味着“非常有限的支持”。 - FranCarstens

32

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
处理程序将在初始创建时触发。然后,每当它变得稍微可见或完全不可见时,它都会被触发。当元素实际上不在视口中可见时,它被认为是不可见的。因此,如果您向下滚动并且元素从屏幕上消失,则观察器将触发,并且// el is not visible代码将被触发-即使元素仍然“显示”(即没有display:nonevisibility:hidden)。重要的是检查元素中是否有任何像素实际上在视口内是可见的。

2
重要提示:IntersectionObserver回调是异步的 => 在调用observer.observe(el)后,您不会立即获得结果; - Liero
这只有在可见性发生变化时才会触发吗? - Mattwmaster58
它会在初始创建时触发吗? - Mattwmaster58
回答Mattwmaster58的问题:是的,它会在初始创建时触发。然后,每次它变得稍微可见或完全不可见时都会触发。当一个元素实际上没有显示在屏幕上时,就被视为不可见。因此,如果您向下滚动并且元素离开屏幕,则观察者将触发,并将触发// el is not visible代码。我将把这个评论添加到答案中。 - joe

23

综合上面几个回答:

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

就像AlexZ所说的,如果您更明确地知道您要寻找的内容,这种方法可能比其他一些选项更慢,但它应该可以捕获所有隐藏元素的主要方式。

但是,这也取决于您认为什么算是可见的。例如,一个div的高度可以设置为0px,但根据溢出属性,其内容仍然可见。或者一个div的内容可以与背景颜色相同,因此对用户不可见,但仍在页面上呈现。或者一个div可能被移动到屏幕外或隐藏在其他div后面,或者其内容可能是不可见的,但边框仍然可见。到某种程度上,“可见”是一个主观的术语。


1
不错,但是style.opacity、style.height和style.width返回的是字符串,所以不能使用!==。 - Maslow
一个元素被隐藏的另一种方式是通过样式,使其颜色与背景颜色相匹配,或者它的 z-index 值比其他元素低。 - nu everest
将"display:none"添加到这里会很好。一个正常工作的解决方案! - Gijo Varghese

9

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

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.*/
    return Boolean(el.offsetParent || el.offsetWidth || el.offsetHeight);
}

顺便提一下:严格来说,"visibility"需要先定义。在我的情况下,只要我能够正常运行所有DOM方法/属性(即使不透明度为0或CSS可见性属性为'hidden'等),我就认为一个元素是可见的。


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