Check if element is visible in DOM

2018-12-31 14:24发布

问题:

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?

回答1:

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.



回答2:

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



回答3:

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 hidden!!\");        
}

jsFiddle



回答4:

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


回答5:

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!



回答6:

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.



回答7:

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)



回答8:

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



回答9:

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:

  • How to tell if a DOM element is visible in the current viewport?
  • How to check if an element is really visible with javascript?

(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.)



回答10:

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.



回答11:

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


回答12:

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


回答13:

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



回答14:

This returns true if and only if the element and all its ancestors are visible. It only looks at the display and visibilitystyle properties:

    var isVisible = function(el){
        // returns true iff el and all its ancestors are visible
        return el.style.display !== \'none\' && el.style.visibility !== \'hidden\'
        && (el.parentElement? isVisible(el.parentElement): true)
    };


回答15:

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.



回答16:

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


回答17:

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>