Pregunta ¿Cómo saber si un elemento DOM está visible en la ventana gráfica actual?


¿Hay una manera eficiente de saber si un elemento DOM (en un documento HTML) está actualmente visible (aparece en el ventana)?

(La pregunta se refiere a Firefox)


747
2017-09-23 21:24


origen


Respuestas:


Actualizar: El tiempo avanza y también nuestros navegadores. Esta técnica ya no es recomendada y debes usar la solución de @ Dan a continuación (https://stackoverflow.com/a/7557433/5628) si no necesita soportar IE <7.

Solución original (ahora desactualizada):

Esto verificará si el elemento es completamente visible en la ventana gráfica actual:

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

Puede modificar esto simplemente para determinar si alguna parte del elemento está visible en la ventana gráfica:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

286
2017-09-24 02:40



Ahora la mayoría de los navegadores apoyo getBoundingClientRect método, que se ha convertido en la mejor práctica. Usar una respuesta anterior es muy lento, no es correcto y tiene varios errores.

La solución seleccionada como correcta es casi nunca preciso. Usted puede Lee mas sobre sus errores.


Esta solución fue probada en IE7 +, iOS5 + Safari, Android2 +, Blackberry, Opera Mobile e IE Mobile 10.


function isElementInViewport (el) {

    //special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
    );
}

Cómo utilizar:

Puede estar seguro de que la función indicada anteriormente devuelve la respuesta correcta en el momento en que se invoca, pero ¿qué ocurre con la visibilidad del elemento de seguimiento como un evento?

Coloque el siguiente código en la parte inferior de su <body> etiqueta:

function onVisibilityChange(el, callback) {
    var old_visible;
    return function () {
        var visible = isElementInViewport(el);
        if (visible != old_visible) {
            old_visible = visible;
            if (typeof callback == 'function') {
                callback();
            }
        }
    }
}

var handler = onVisibilityChange(el, function() {
    /* your code go here */
});


//jQuery
$(window).on('DOMContentLoaded load resize scroll', handler); 

/* //non-jQuery
if (window.addEventListener) {
    addEventListener('DOMContentLoaded', handler, false); 
    addEventListener('load', handler, false); 
    addEventListener('scroll', handler, false); 
    addEventListener('resize', handler, false); 
} else if (window.attachEvent)  {
    attachEvent('onDOMContentLoaded', handler); // IE9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

Si realiza alguna modificación DOM, puede cambiar la visibilidad de su elemento, por supuesto.

Pautas y errores comunes:

¿Quizás necesites rastrear el pellizco de la página zoom / dispositivo móvil? jQuery debería manejar zoom / pellizco navegador cruzado, de lo contrario primero o segundo El enlace debería ayudarte.

Si tu modificar DOM, puede afectar la visibilidad del elemento. Deberías tomar el control de eso y llamar handler() a mano. Lamentablemente, no tenemos ningún navegador cruzado onrepaint evento. Por otro lado, eso nos permite realizar optimizaciones y volver a verificar solo las modificaciones DOM que pueden cambiar la visibilidad del elemento.

Nunca jamás Úselo dentro de jQuery $ (documento) .ready () Solo porque no hay garantía CSS se ha aplicado en este momento. Su código puede funcionar localmente con su CSS en el disco duro, pero una vez que lo coloque en el servidor remoto, fallará.

Después DOMContentLoaded Está despedido, los estilos son aplicados, pero las imágenes no están cargadas todavía. Entonces, deberíamos agregar window.onload oyente de eventos.

No podemos atrapar el evento zoom / pinch aún.

El último recurso podría ser el siguiente código:

/* TODO: this looks like a very bad code */
setInterval(handler, 600); 

Puede usar una función increíble pageVisibiliy API HTML5 si le importa si la pestaña con su página web está activa y visible.

TODO: este método no maneja dos situaciones:


1163
2018-03-04 14:18



Actualizar

En los navegadores modernos, es posible que desee verificar el Intersection Observer API que proporciona los siguientes beneficios:

  • Mejor rendimiento que escuchar eventos de desplazamiento
  • Funciona en iframes de dominio cruzado
  • Puede decir si un elemento está obstruyendo / intersectando a otro

Intersection Observer está en camino de convertirse en un estándar completo y ya es compatible con Chrome 51+, Edge 15+ y Firefox 55+ y está en desarrollo para Safari. También hay un polyfill disponible.


Respuesta anterior

Hay algunos problemas con el respuesta provista por Dan eso podría hacer que sea un enfoque inadecuado para algunas situaciones. Algunos de estos problemas se señalan en su respuesta cerca de la parte inferior, que su código dará falsos positivos para los elementos que son:

  • Escondido por otro elemento frente al que se está probando
  • Fuera del área visible de un elemento padre o ancestro
  • Un elemento o sus hijos ocultos mediante el uso de CSS clip propiedad

Estas limitaciones se demuestran en los siguientes resultados de prueba simple:

Failed test, using isElementInViewport

La solución: isElementVisible()

Aquí hay una solución a esos problemas, con el resultado de la prueba a continuación y una explicación de algunas partes del código.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

Pasando la prueba:  http://jsfiddle.net/AndyE/cAY8c/

Y el resultado:

Passed test, using isElementVisible

Notas adicionales

Sin embargo, este método no carece de sus propias limitaciones. Por ejemplo, un elemento que se prueba con un índice z más bajo que otro elemento en la misma ubicación se identificará como oculto incluso si el elemento que está delante no oculta realmente ninguna parte de él. Aún así, este método tiene sus usos en algunos casos que la solución de Dan no cubre.

Ambos element.getBoundingClientRect() y document.elementFromPoint() son parte de la especificación del borrador de trabajo de CSSOM y son compatibles con al menos IE 6 y posterior más navegadores de escritorio durante mucho tiempo (aunque no perfectamente). Ver Quirksmode en estas funciones para más información.

contains() se usa para ver si el elemento devuelto por document.elementFromPoint() es un nodo secundario del elemento que estamos probando para visibilidad. También devuelve verdadero si el elemento devuelto es el mismo elemento. Esto solo hace que el cheque sea más robusto. Es compatible con todos los principales navegadores, Firefox 9.0 es el último de ellos en agregarlo. Para obtener un soporte de Firefox anterior, consulte el historial de esta respuesta.

Si desea probar más puntos alrededor del elemento para obtener visibilidad, es decir, para asegurarse de que el elemento no esté cubierto por más de, digamos, 50%, no se necesitaría mucho para ajustar la última parte de la respuesta. Sin embargo, tenga en cuenta que probablemente sea muy lento si comprueba cada píxel para asegurarse de que sea 100% visible.


120
2018-04-29 02:36



Lo intenté La respuesta de Dan  pero el álgebra utilizada para determinar los límites es incorrecta. la respuesta de Ryan está más cerca, pero el elemento que se está probando debe estar dentro de la ventana gráfica al menos en 1 píxel, así que pruebe esta función:

function isElementInViewport(el) {
    var rect = el.getBoundingClientRect();

    return rect.bottom > 0 &&
        rect.right > 0 &&
        rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
        rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}

43
2018-05-03 17:00



Hay un plugin jQuery llamado en vista eso hace el trabajo


25
2017-09-25 12:54



Como un servicio público:
La respuesta de Dan con los cálculos correctos (el elemento puede ser> una ventana, especialmente en pantallas de teléfonos móviles), y corrige las pruebas de jQuery, además de agregar isElementPartiallyInViewport:

Por cierto, el diferencia entre window.innerWidth y document.documentElement.clientWidth es que clientWidth / clientHeight no incluye la barra de desplazamiento, mientras que window.innerWidth / Height sí lo hace.

function isElementPartiallyInViewport(el)
{
    //special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) el = el[0];

    var rect = el.getBoundingClientRect();
    // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
    var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
    var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

    return (vertInView && horInView);
}


// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el) 
{
    //special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) el = el[0];

    var rect = el.getBoundingClientRect();
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    return (
           (rect.left >= 0)
        && (rect.top >= 0)
        && ((rect.left + rect.width) <= windowWidth)
        && ((rect.top + rect.height) <= windowHeight)
    );

}


function fnIsVis(ele)
{
    var inVpFull = isElementInViewport(ele);
    var inVpPartial = isElementPartiallyInViewport(ele);
    console.clear();
    console.log("Fully in viewport: " + inVpFull);
    console.log("Partially in viewport: " + inVpPartial);
}

Caso de prueba

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Test</title>
    <!--
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>    
    <script src="scrollMonitor.js"></script>
    -->

    <script type="text/javascript">

        function isElementPartiallyInViewport(el)
        {
            //special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) el = el[0];

            var rect = el.getBoundingClientRect();
            // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
            var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
            var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

            return (vertInView && horInView);
        }


        // http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
        function isElementInViewport (el) 
        {
            //special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) el = el[0];


            var rect = el.getBoundingClientRect();
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            return (
                   (rect.left >= 0)
                && (rect.top >= 0)
                && ((rect.left + rect.width) <= windowWidth)
                && ((rect.top + rect.height) <= windowHeight)
            );

        }


        function fnIsVis(ele)
        {
            var inVpFull = isElementInViewport(ele);
            var inVpPartial = isElementPartiallyInViewport(ele);
            console.clear();
            console.log("Fully in viewport: " + inVpFull);
            console.log("Partially in viewport: " + inVpPartial);
        }


        // var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
        // var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;

    </script>

</head>
<body>

    <div style="display: block; width: 2000px; height: 10000px; background-color: green;">

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
        <div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
        t
        </div>

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />

    </div>

    <!--
    <script type="text/javascript">

        var element = document.getElementById("myele");
        var watcher = scrollMonitor.create( element );

        watcher.lock();

        watcher.stateChange(function() {
            console.log("state changed");
            // $(element).toggleClass('fixed', this.isAboveViewport)
        });

    </script>
    -->
</body>
</html>

25
2017-09-14 05:49



Ver la fuente de borde, que usa getBoundingClientRect. Es como:

function inViewport (el) {

    var r, html;
    if ( !el || 1 !== el.nodeType ) { return false; }
    html = document.documentElement;
    r = el.getBoundingClientRect();

    return ( !!r 
      && r.bottom >= 0 
      && r.right >= 0 
      && r.top <= html.clientHeight 
      && r.left <= html.clientWidth 
    );

}

Devoluciones true Si alguna parte del elemento está en la ventana gráfica.


23
2017-08-02 13:40