453

Is there any way that I can check if an element is visible in pure JS (no jQuery) ?

So, for example, in this page: Performance Bikes, if you hover over Deals (on the top menu), a window of deals appear, but at the beginning it was not shown. It is in the HTML but it is not visible.

So, given a DOM element, how can I check if it is visible or not? I tried:

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

but it doesn't seem to be working. I wonder which attributes should I check. It comes to my mind:

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

Any others that I might be missing?

Sunil Garg
  • 10,122
  • 12
  • 92
  • 133
Hommer Smith
  • 22,976
  • 49
  • 140
  • 254
  • 1
    That doesn't use display, it uses visibility so check for visibility (hidden or visible). ex: `document.getElementById('snDealsPanel').style.visibility` – PSL Oct 29 '13 at 21:50
  • PSL. If I would like to do this more general, which attributes should I check: visibility, display...? – Hommer Smith Oct 29 '13 at 21:51
  • You can make it generic in your own way but what i am saying is it uses visibility inspecting the element. – PSL Oct 29 '13 at 21:51
  • Here is my code (no jquery) for this question http://stackoverflow.com/a/22969337/2274995 – Aleko Apr 09 '14 at 17:11
  • Link is broken which makes your question not easy to understand. Kindly re-frame it. – yogihosting Jun 08 '20 at 12:08
  • 0 < document.querySelector('foo').getBoundingClientRect().height – Time Killer Apr 16 '21 at 09:04

20 Answers20

739

According to this MDN documentation, an element's offsetParent property will return null whenever it, or any of its parents, is hidden via the display style property. Just make sure that the element isn't fixed. A script to check this, if you have no position: fixed; elements on your page, might look like:

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

On the other hand, if you do have position fixed elements that might get caught in this search, you will sadly (and slowly) have to use window.getComputedStyle(). The function in that case might be:

// 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')
}

Option #2 is probably a little more straightforward since it accounts for more edge cases, but I bet its a good deal slower, too, so if you have to repeat this operation many times, best to probably avoid it.

7ochem
  • 2,035
  • 1
  • 29
  • 37
AlexZ
  • 9,637
  • 3
  • 24
  • 39
  • Wow no kidding. Imo there should be no reason to use the second method as a universal solution; no page should have fixed elements that its creator isn't explicitly aware of, and one can just check those manually using the getComputedStyle() method after running the offsetParent method on all elements first. – AlexZ Mar 18 '14 at 07:10
  • 6
    Also FYI, just discovered that `el.offsetParent` wasn't working on IE9 for non-fixed elements. Or so it seems, anyway. (OK for IE11, though.) So went with `getComputedStyle` after all. – Nick Mar 31 '14 at 09:12
  • the MDN documentatio refered to says "This property will return null on Webkit if the element is hidden [...]". It also mentions IE9 but how about other browsers? – ithil Oct 14 '14 at 14:41
  • This solution doesn't work for sliders. Mainly slider libs use a mask layer to show the current item to the user and the element always has a parent and `offsetParent` is not empty. – Afshin Mehrabani Feb 26 '15 at 14:53
  • Note that offsetParent is marked as causing reflow in browsers. Ref: http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html So, using the 2nd method will be effectively better. – Ethan Mar 01 '15 at 23:42
  • @Ethan: not so sure, don't have anything to back this up, but I feel like `window.getComputedStyle()` does some extra calculation under the hood that slows it down. Regardless, the jsPerf above speaks for itself... – AlexZ Mar 02 '15 at 00:17
  • 1
    @AlexZ I am not sure if offsetParent really does a reflow in today's browsers, but yes a couple of yrs back, they used to, that's what I understand from reports. Note that the jsPerf only mentions of the speed of execution, while reflow is about the display. And reflows does make the UI poor. I personally will not go for the speed for a routine that is probably called 5/6 times on a page. – Ethan Mar 02 '15 at 00:50
  • Fair point about the reflow vs JS performance. I was under the impression that reflow-calling properties/methods were such because it was calculating their value that required the reflow, and that they would not be able to return until said reflow is complete. I could definitely be wrong though. Either way, in the project I used this for, `offsetParent` poved to be much more performant from the UI standpoint (it was the only method that caused no visible lag). Whether this was due to faster JS execution or less layout thrashing, I don't know. – AlexZ Mar 02 '15 at 01:05
  • 2
    alas! `getComputedStyle` does not work correctly: http://plnkr.co/edit/6CSCA2fe4Gqt4jCBP2wu?p=preview However, so does `offsetParent` - perhaps a combination of the two should be used? – guy mograbi Oct 31 '15 at 18:16
  • @guymograbi, I think getComputedStyle() works as specified: it tells you the _computed_ style of the element. To figure out how the element is actually _rendered_, you would need to recurse through all of its parents. On the other hand, I think offsetParent takes the rendered style into account. – AlexZ Nov 02 '15 at 11:03
  • @AlexZ I just wanted code like `$(..).is(':visible')` but without jquery.. you can see my answer as to what I used (simply copy pasted what jquery do) - no iteration on parents.. I think my solution answers all my use-cases but perhaps HommerSmith had other needs. – guy mograbi Nov 03 '15 at 15:59
  • so sad this is still so complex – SuperUberDuper Mar 07 '16 at 16:20
  • so for `getComputedStyle` we also have to check the whole parents dom tree? – SuperUberDuper Mar 07 '16 at 16:21
  • 2
    for ie9+ie10 you can check if offsetParent = body for non visible elements. – SuperUberDuper Mar 07 '16 at 16:42
  • 1
    i am seeing `getComputedStyle(element).display` having the value of `table` in some elements (for instance TABLE elements) whose ancestor is `display:none` making is essentially useless. – Michael May 12 '16 at 23:21
  • You may wish to add that `` elements do not have offsetParent. Source 1: https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent ; Source 2: http://www.quirksmode.org/dom/w3c_cssom.html – zanetu Aug 15 '16 at 04:12
  • 3
    Just a note on the window.getComputedStyle method, it returns a _live_ CSSStyleDeclaration object. So you only have to grab the style once, then can re-check the object if you have to perform the check multiple times. – abenbot Oct 15 '18 at 14:22
  • Be sure to check if the element is visible by `style.visible === "hidden"` – konsalex Dec 17 '19 at 20:02
  • offsetParent can be "undefined". So it's better to check `!!element.offsetParent === true` or just `!!element.offsetParent` – Oboroten Nov 25 '20 at 19:43
  • This solution will *not* work if the element or any of its ancestors has `visibility: hidden`. – Tigran Dec 24 '20 at 17:21
120

All the other solutions broke for some situation for me..

See the winning answer breaking at:

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

Eventually, I decided that the best solution was $(elem).is(':visible') - however, this is not pure javascript. it is jquery..

so I peeked at their source and found what I wanted

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

This is the source: https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js

guy mograbi
  • 22,955
  • 13
  • 75
  • 115
  • 12
    This will return `true` for an element with `visibility:hidden` – Yuval A. Jun 08 '16 at 11:25
  • 10
    @YuvalA.: Yeah because the element is still visible. Setting a element to `visibility:hidden` shows no content anymore, but still takes the width and height of the element! – Jacob van Lingen Mar 09 '17 at 08:18
  • 5
    @Michael you can easily browse the jQuery code and if you are using any modern IDE (try it if not) you can jump to correct code parts while using jQuery or any other lib. You can learn a lot while browsing codebases of open source projects. – Lukas Liesis May 15 '17 at 21:04
  • OP asked for a solution with no jQuery – Doug Wilhelm Dec 08 '20 at 20:30
  • For anyone looking for a version that also works with `visibility:hidden`, adding `&& window.getComputedStyle(elem).visibility !== "hidden"` to the end of this return line seems to work – dkniffin Dec 09 '20 at 00:41
70

If you're interested in visible by the user:

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

Tested on (using mocha terminology):

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;
    });
});
Ohad Navon
  • 1,166
  • 11
  • 19
  • an edge case if the elem is positioned outside of the viewport, can be caught by "if (!pointContainer) return false;" checking the first pointContainer – Jerry Deng Jun 13 '19 at 17:52
  • 1
    If you want to check whether the user could possibly see it, you would have to use a `scrollIntoView` right?! This is quite expensive. Is there another clever way? – Kim Kern May 19 '20 at 08:41
  • What about a case when overlapping element (to be precise: all overlapping elements) has opacity < 1? – Shimon S Sep 29 '20 at 14:05
37

This may help : Hide the element by positioning it on far most left position and then check the offsetLeft property. If you want to use jQuery you can simply check the :visible selector and get the visibility state of the element.

HTML :

<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

Harry B
  • 2,511
  • 1
  • 17
  • 40
Md. Ashaduzzaman
  • 3,808
  • 2
  • 16
  • 32
  • 17
    The OP requests a no-jQuery answer. – Stephen Quan Oct 29 '13 at 22:22
  • It was edited later i guess. When I answered it wasn't mentioned in the thread. – Md. Ashaduzzaman Oct 29 '13 at 22:23
  • 3
    @StephenQuan, I've updated the answer with both jQuery and javaScript solution. – Md. Ashaduzzaman Oct 29 '13 at 22:43
  • 6
    for the jQuery example, shouldn't the alert say "Div is visible?" – Andrei Bazanov Feb 10 '16 at 11:36
  • I wouldn't want to conclude an element is entirely hidden just because its offsetLeft is less than 0: what if it's only a small number of pixels less than 0 and its right-hand part is visible? (I agree this would seem like a silly design, but you never know with web designers these days.) It's probably better to add the width to offsetLeft and see if the result is still less than 0. Just in case. – Silas S. Brown Feb 20 '18 at 12:15
  • I am glad there is multiple solutions despite asking for no-jquery. Future readers with or without jquery like me get also get an answer here and choose :D – Joe DF Oct 10 '19 at 14:23
  • A screen reader will still pick up content that is moved visually off-screen. It is, in fact, an accepted method of handling screen reader only content. – Jack B Oct 21 '19 at 13:40
37

Use the same code as jQuery does:

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

So, in a function:

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

Works like a charm in my Win/IE10, Linux/Firefox.45, Linux/Chrome.52...

Many thanks to jQuery without jQuery!

Yvan
  • 2,239
  • 22
  • 27
  • Nice but doesn't cover elements being hidden by overflow. – Slava Jun 29 '17 at 20:40
  • 1
    nice but what why !! (double negation)? – Sunil Garg May 18 '18 at 07:06
  • 5
    To force the result as boolean. As `e.offsetWidth` is an integer, `!e.offsetWidth` will return `false` if `e.offsetWidth` is higher than zero (element is visible). So adding another `!` as in `!!e.offsetWidth` will return `true` if `e.offsetWidth` is higher than zero. It's a shorthand for `return e.offsetWidth > 0 ? true : false` or obviously `return e.offsetWidth > 0`. – Yvan May 19 '18 at 07:57
19

Combining a couple answers above:

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

Like AlexZ said, this may be slower than some of your other options if you know more specifically what you're looking for, but this should catch all of the main ways elements are hidden.

But, it also depends what counts as visible for you. Just for example, a div's height can be set to 0px but the contents still visible depending on the overflow properties. Or a div's contents could be made the same color as the background so it is not visible to users but still rendered on the page. Or a div could be moved off screen or hidden behind other divs, or it's contents could be non-visible but the border still visible. To a certain extent "visible" is a subjective term.

iota
  • 34,586
  • 7
  • 32
  • 51
Matthew
  • 3,560
  • 1
  • 19
  • 44
  • 1
    Nice, but style.opacity, style.height and style.width return a string so it won't work with !== – Maslow Apr 08 '16 at 09:03
  • Another way that an element could be hidden via style is that it's color could match the background color, or it's z-index could be lower than other elements. – nu everest Mar 27 '17 at 00:57
  • adding `display:none` to this would be great. A proper working solution! – Gijo Varghese Nov 07 '18 at 12:53
17

The accepted answer did not worked for me.

Year 2020 breakdown.

  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://stackoverflow.com/a/11639664/4481831 . 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, see more here

    Demo:

    console.log(document.querySelector('#div1').clientHeight); //not zero
    console.log(document.querySelector('#span1').clientHeight); //zero
    <div id="div1">test1 div</div>
    <span id="span1">test2 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>

Conclusion.

So what I have showed you is that no method is perfect. To make a proper visibility check, you must use a combination of the last 3 methods.

Regular Jo
  • 4,348
  • 3
  • 18
  • 36
crisc2000
  • 490
  • 5
  • 15
7

I've got a more performant solution compared to AlexZ's getComputedStyle() solution when one has position 'fixed' elements, if one is willing to ignore some edge cases (check comments):

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

Side note: Strictly speaking, "visibility" needs to be defined first. In my case, I am considering an element visible as long as I can run all DOM methods/properties on it without problems (even if opacity is 0 or CSS visibility property is 'hidden' etc).

Community
  • 1
  • 1
Munawwar
  • 1,394
  • 17
  • 14
6

If element is regular visible (display:block and visibillity:visible), but some parent container is hidden, then we can use clientWidth and clientHeight for check that.

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

Plunker (click here)

Vlada
  • 1,404
  • 18
  • 22
  • `ele.style.visibility !== 'hidden'` is redundant here. In that case, clientWidth and clientHeight will be 0. – subdavis Apr 08 '18 at 12:56
  • Opacity check needs to be coerced to number, because `style` values are all strings. Eg, `parseFloat(ele.style.opacity) < 0.1` – avalanche1 Nov 25 '20 at 13:26
5

So what I found is the most feasible method:

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

This is build on these facts:

  • A display: none element (even a nested one) doesn't have a width nor height.
  • visiblity is hidden even for nested elements.

So no need for testing offsetParent or looping up in the DOM tree to test which parent has visibility: hidden. This should work even in IE 9.

You could argue if opacity: 0 and collapsed elements (has a width but no height - or visa versa) is not really visible either. But then again they are not per say hidden.

Tokimon
  • 3,482
  • 1
  • 16
  • 22
5

A little addition to ohad navon's answer.

If the center of the element belongs to the another element we won't find it.

So to make sure that one of the points of the element is found to be visible

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;
}
Guy Messika
  • 149
  • 1
  • 11
4

If we're just collecting basic ways of detecting visibility, let me not forget:

opacity > 0.01; // probably more like .1 to actually be visible, but YMMV

And as to how to obtain attributes:

element.getAttribute(attributename);

So, in your example:

document.getElementById('snDealsPanel').getAttribute('visibility');

But wha? It doesn't work here. Look closer and you'll find that visibility is being updated not as an attribute on the element, but using the style property. This is one of many problems with trying to do what you're doing. Among others: you can't guarantee that there's actually something to see in an element, just because its visibility, display, and opacity all have the correct values. It still might lack content, or it might lack a height and width. Another object might obscure it. For more detail, a quick Google search reveals this, and even includes a library to try solving the problem. (YMMV)

Check out the following, which are possible duplicates of this question, with excellent answers, including some insight from the mighty John Resig. However, your specific use-case is slightly different from the standard one, so I'll refrain from flagging:

(EDIT: OP SAYS HE'S SCRAPING PAGES, NOT CREATING THEM, SO BELOW ISN'T APPLICABLE) A better option? Bind the visibility of elements to model properties and always make visibility contingent on that model, much as Angular does with ng-show. You can do that using any tool you want: Angular, plain JS, whatever. Better still, you can change the DOM implementation over time, but you'll always be able to read state from the model, instead of the DOM. Reading your truth from the DOM is Bad. And slow. Much better to check the model, and trust in your implementation to ensure that the DOM state reflects the model. (And use automated testing to confirm that assumption.)

Community
  • 1
  • 1
XML
  • 18,278
  • 7
  • 60
  • 64
4

Improving on @Guy Messika's answer above, breaking and returning false if the center point' X is < 0 is wrong as the element right side may go into the view. here's a fix:

private isVisible(elem) {
    const style = getComputedStyle(elem);

    if (style.display === 'none') return false;
    if (style.visibility !== 'visible') return false;
    if ((style.opacity as any) === 0) return false;

    if (
        elem.offsetWidth +
        elem.offsetHeight +
        elem.getBoundingClientRect().height +
        elem.getBoundingClientRect().width === 0
    ) return false;

    const elementPoints = {
        center: {
            x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
            y: elem.getBoundingClientRect().top + elem.offsetHeight / 2,
        },
        topLeft: {
            x: elem.getBoundingClientRect().left,
            y: elem.getBoundingClientRect().top,
        },
        topRight: {
            x: elem.getBoundingClientRect().right,
            y: elem.getBoundingClientRect().top,
        },
        bottomLeft: {
            x: elem.getBoundingClientRect().left,
            y: elem.getBoundingClientRect().bottom,
        },
        bottomRight: {
            x: elem.getBoundingClientRect().right,
            y: elem.getBoundingClientRect().bottom,
        },
    };

    const docWidth = document.documentElement.clientWidth || window.innerWidth;
    const docHeight = document.documentElement.clientHeight || window.innerHeight;

    if (elementPoints.topLeft.x > docWidth) return false;
    if (elementPoints.topLeft.y > docHeight) return false;
    if (elementPoints.bottomRight.x < 0) return false;
    if (elementPoints.bottomRight.y < 0) return false;

    for (let index in elementPoints) {
        const point = elementPoints[index];
        let pointContainer = document.elementFromPoint(point.x, point.y);
        if (pointContainer !== null) {
            do {
                if (pointContainer === elem) return true;
            } while (pointContainer = pointContainer.parentNode);
        }
    }
    return false;
}
Israel
  • 1,044
  • 2
  • 12
  • 25
3

Just for the reference it should be noted that getBoundingClientRect() can work in certain cases.

For example, a simple check that the element is hidden using display: none could look somewhat like this:

var box = element.getBoundingClientRect();
var visible = box.width && box.height;

This is also handy because it also covers zero-width, zero-height and position: fixed cases. However, it shall not report elements hidden with opacity: 0 or visibility: hidden (but neither would offsetParent).

BorisOkunskiy
  • 1,752
  • 1
  • 16
  • 24
  • 1
    Best answer for me... simple and effective. And no single upvote after 3 years! Goes on to show the worth of “crowd wisdom”. My version: `var isVisible = el => (r => r.width && r.height)(el.getBoundingClientRect());`. Then I can filter arrays of elements in the following way: `$$(sel).filter(isVisible)`. – 7vujy0f0hy Jun 18 '18 at 03:33
  • I find it the simplest solution https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect – Marian07 Feb 01 '19 at 20:39
  • This does not work when visible to the user.... if you scroll away it will remain true – Ray Foss Jun 27 '19 at 20:56
  • @RayFoss you do understand that there are various definitions to "visible", right? :) Just to make it clear "Visible in DOM" or "Visible on screen" or "Visible in viewport" or "Visible within the container with scrollbars" — all of those mean different things and implementations. Also, how visible is the element? Is it only visible when all of its pixels are rendered? (what about transforms?) Or, otherwise, is it visible if only its "conceptually transparent" padding is currently on screen? To summarize, I think the definition will depend a lot about your specific use case. – BorisOkunskiy Aug 16 '20 at 21:38
2

The jQuery code from http://code.jquery.com/jquery-1.11.1.js has an isHidden param

var isHidden = function( elem, el ) {
    // isHidden might be called from jQuery#filter function;
    // in that case, element will be second argument
    elem = el || elem;
    return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
};

So it looks like there is an extra check related to the owner document

I wonder if this really catches the following cases:

  1. Elements hidden behind other elements based on zIndex
  2. Elements with transparency full making them invisible
  3. Elements positioned off screen (ie left: -1000px)
  4. Elements with visibility:hidden
  5. Elements with display:none
  6. Elements with no visible text or sub elements
  7. Elements with height or width set to 0
Scott Izu
  • 1,901
  • 21
  • 11
1

To elaborate on everyone's great answers, here is the implementation that was used in the Mozilla Fathom project:

/**
 * Yield an element and each of its ancestors.
 */
export function *ancestors(element) {
    yield element;
    let parent;
    while ((parent = element.parentNode) !== null && parent.nodeType === parent.ELEMENT_NODE) {
        yield parent;
        element = parent;
    }
}

/**
 * Return whether an element is practically visible, considering things like 0
 * size or opacity, ``visibility: hidden`` and ``overflow: hidden``.
 *
 * Merely being scrolled off the page in either horizontally or vertically
 * doesn't count as invisible; the result of this function is meant to be
 * independent of viewport size.
 *
 * @throws {Error} The element (or perhaps one of its ancestors) is not in a
 *     window, so we can't find the `getComputedStyle()` routine to call. That
 *     routine is the source of most of the information we use, so you should
 *     pick a different strategy for non-window contexts.
 */
export function isVisible(fnodeOrElement) {
    // This could be 5x more efficient if https://github.com/w3c/csswg-drafts/issues/4122 happens.
    const element = toDomElement(fnodeOrElement);
    const elementWindow = windowForElement(element);
    const elementRect = element.getBoundingClientRect();
    const elementStyle = elementWindow.getComputedStyle(element);
    // Alternative to reading ``display: none`` due to Bug 1381071.
    if (elementRect.width === 0 && elementRect.height === 0 && elementStyle.overflow !== 'hidden') {
        return false;
    }
    if (elementStyle.visibility === 'hidden') {
        return false;
    }
    // Check if the element is irrevocably off-screen:
    if (elementRect.x + elementRect.width < 0 ||
        elementRect.y + elementRect.height < 0
    ) {
        return false;
    }
    for (const ancestor of ancestors(element)) {
        const isElement = ancestor === element;
        const style = isElement ? elementStyle : elementWindow.getComputedStyle(ancestor);
        if (style.opacity === '0') {
            return false;
        }
        if (style.display === 'contents') {
            // ``display: contents`` elements have no box themselves, but children are
            // still rendered.
            continue;
        }
        const rect = isElement ? elementRect : ancestor.getBoundingClientRect();
        if ((rect.width === 0 || rect.height === 0) && elementStyle.overflow === 'hidden') {
            // Zero-sized ancestors don’t make descendants hidden unless the descendant
            // has ``overflow: hidden``.
            return false;
        }
    }
    return true;
}

It checks on every parent's opacity, display, and rectangle.

LeeNeverGup
  • 1,066
  • 8
  • 22
0

Here's the code I wrote to find the only visible among a few similar elements, and return the value of its "class" attribute without jQuery:

  // Build a NodeList:
  var nl = document.querySelectorAll('.myCssSelector');

  // convert it to array:
  var myArray = [];for(var i = nl.length; i--; myArray.unshift(nl[i]));

  // now find the visible (= with offsetWidth more than 0) item:
  for (i =0; i < myArray.length; i++){
    var curEl = myArray[i];
    if (curEl.offsetWidth !== 0){
      return curEl.getAttribute("class");
    }
  }
0

This is what I did:

HTML & CSS: Made the element hidden by default

<html>
<body>

<button onclick="myFunction()">Click Me</button>

<p id="demo" style ="visibility: hidden;">Hello World</p> 

</body>
</html> 

JavaScript: Added a code to check whether the visibility is hidden or not:

<script>
function myFunction() {
   if ( document.getElementById("demo").style.visibility === "hidden"){
   document.getElementById("demo").style.visibility = "visible";
   }
   else document.getElementById("demo").style.visibility = "hidden";
}
</script>
0

Using jquery $("your-selector:visible").length; //return 1 if visible else 0 e.g $(".custom-control-input:visible").length; //if this element is visible on screen it will return 1

Nilesh Lathe
  • 137
  • 4
-2

This is a way to determine it for all css properties including visibility:

html:

<div id="element">div content</div>

css:

#element
{
visibility:hidden;
}

javascript:

var element = document.getElementById('element');
 if(element.style.visibility == 'hidden'){
alert('hidden');
}
else
{
alert('visible');
}

It works for any css property and is very versatile and reliable.

William Green
  • 75
  • 1
  • 11