Búsqueda de sitios web

javascript - Verificar si un elemento es visible en el DOM


¿Hay alguna manera de comprobar si un elemento es visible en JavaScript puro (sin jQuery)?

Entonces, dado un elemento del DOM, ¿cómo puedo comprobar si es visible o no? Intenté:

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

pero no parece estar funcionando. Me pregunto qué atributos debería revisar. Me viene a la mente:

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

¿Alguno más que pueda estar pasando por alto?

Respuesta #1

Según esta documentación de MDN, la propiedad offsetParent de un elemento devolverá null siempre que él, o cualquiera de sus elementos padre, esté oculto mediante la propiedad de estilo display. Solo asegúrate de que el elemento no esté fijado. Un script para comprobar esto, si no tienes elementos con position: fixed; en tu página, podría ser:

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

Por otro lado, si tienes elementos con posición fija que podrían verse atrapados en esta búsqueda, lamentablemente (y lentamente) tendrás que usar window.getComputedStyle(). La función en ese caso podría ser:

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

La opción #2 probablemente sea un poco más directa ya que tiene en cuenta más casos extremos, pero apuesto a que también es considerablemente más lenta, así que si tienes que repetir esta operación muchas veces, lo mejor probablemente sea evitarla.

Respuesta #2

Todas las demás soluciones fallaron en alguna situación para mí..

Mira la respuesta ganadora rompiendo en:

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

Finalmente, decidí que la mejor solución era $ (elem).is(':visible') - sin embargo, esto no es javascript puro. es jquery..

así que eché un vistazo a su fuente y encontré lo que quería

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

Esta es la fuente: https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js

Respuesta #3

Si estás interesado en que sea visible para el usuario:

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

Probado en (usando terminología de 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;
    });
});

Respuesta #4

Los navegadores modernos han añadido Element.checkVisibility().
Devuelve true si el elemento es visible, y false en caso contrario.

La función verifica una variedad de factores que harían que un elemento sea invisible, incluyendo display:none, visibility, content-visibility y opacity:

let element = document.getElementById("myIcon");
let isVisible = element.checkVisibility({
    opacityProperty: true,   // Check CSS opacity property too
    visibilityProperty: true // Check CSS visibility property too
});

Nota al margen: checkVisibility() antes se llamaba isVisible(). Consulta este problema en GitHub.
Consulta el borrador de la especificación de checkVisibility() aquí.

Respuesta #5

Usa el mismo código que jQuery usa:

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

Entonces, en una función:

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

Funciona de maravilla en mi Win/IE10, Linux/Firefox.45, Linux/Chrome.52...

¡Muchas gracias a jQuery sin jQuery!

Respuesta #6

La respuesta aceptada no funcionó para mí.

Desglose del año 2020.

  1. El método (elem.offsetParent !== null) funciona bien en Firefox pero no en Chrome. En Chrome position: fixed también hará que offsetParent devuelva null incluso si el elemento es visible en la página.

    El usuario Phrogz realizó una prueba grande (2,304 divs) en elementos con propiedades variables para demostrar el problema. https://stackoverflow.com/a/11639664/4481831 . Ejecutarlo con varios navegadores para ver las diferencias.

    Demostración :

    //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. El (getComputedStyle(elem).display !== 'none') no funciona porque el elemento puede ser invisible debido a que una de las propiedades display de los padres está establecida en none, getComputedStyle no detectará eso.

    Demostración :

    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. El (elem.clientHeight !== 0). Este método no se ve influenciado por position: fixed y también verifica si los elementos padres no son visibles. Pero tiene problemas con elementos simples que no tienen un diseño CSS y elementos en línea, vea más aquí

    Demostración :

    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. El (elem.getClientRects().length !== 0) puede parecer que resuelve los problemas de los 3 métodos anteriores. Sin embargo, tiene problemas con elementos que usan trucos de CSS (distintos de display: none) para ocultarse en la página.

    Demostración

    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>

Conclusión.

Entonces, lo que les he mostrado es que ningún método es perfecto. Para hacer una verificación adecuada de visibilidad, debes usar una combinación de los últimos 3 métodos.

Respuesta #7

Esto puede ayudar: Oculta el elemento posicionándolo en la posición más a la izquierda y luego verifica la propiedad offsetLeft. Si deseas usar jQuery, simplemente puedes verificar el selector :visible y obtener el estado de visibilidad del elemento.

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

Respuesta #8

solución 2021

Según la documentación de MDN, un observador de interacción observa de manera asincrónica los cambios en la intersección de un elemento objetivo con un elemento ancestro o con el viewport de un documento de nivel superior. Esto significa que cada vez que el elemento se cruza con el viewport, el observador de interacción se activará.

A partir de 2021, todos los navegadores actuales soportan el observador de intersección, excepto IE.

Implementación

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 manejador se activará cuando se cree inicialmente. Y luego se activará cada vez que se vuelva ligeramente visible o completamente no visible. Un elemento se considera no visible cuando en realidad no es visible dentro del área de visualización. Así que si haces scroll hacia abajo y el elemento sale de la pantalla, entonces el observador se activará y se ejecutará el código // el no es visible, incluso si el elemento todavía está "mostrado" (es decir, no tiene display:none o visibility:hidden). Lo que importa es si hay algún píxel del elemento que esté realmente visible dentro del área de visualización.

Respuesta #9

Combinando un par de respuestas anteriores:

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

Como dijo AlexZ, esto puede ser más lento que algunas de tus otras opciones si sabes más específicamente lo que estás buscando, pero esto debería captar todas las formas principales en que los elementos están ocultos.

Pero, también depende de lo que cuente como visible para ti. Solo como ejemplo, la altura de un div puede establecerse en 0px pero el contenido aún puede ser visible dependiendo de las propiedades de desbordamiento. O el contenido de un div podría hacerse del mismo color que el fondo, por lo que no es visible para los usuarios pero aún se renderiza en la página. O un div podría ser movido fuera de la pantalla o oculto detrás de otros divs, o su contenido podría no ser visible pero el borde todavía visible. Hasta cierto punto, "visible" es un término subjetivo.

Respuesta #10

Tengo una solución más eficiente en comparación con la solución getComputedStyle() de AlexZ cuando se tienen elementos con posición 'fixed', si uno está dispuesto a ignorar algunos casos extremos (ver comentarios):

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

Nota al margen: Estrictamente hablando, primero es necesario definir la "visibilidad". En mi caso, estoy considerando un elemento visible siempre que pueda ejecutar todos los métodos/propiedades del DOM en él sin problemas (incluso si la opacidad es 0 o la propiedad de visibilidad de CSS es 'hidden', etc.).

Artículos relacionados