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


950

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

(La pregunta se refiere a Firefox).


Depende de lo que quieras decir con visible. Si quiere decir que se muestra actualmente en la página, dada la posición de desplazamiento, puede calcularlo en función de los elementos y desplazamiento y la posición de desplazamiento actual.
roryf

18
Aclaración: Visible, como en el rectángulo que se muestra actualmente. Visible, como en no oculto o pantalla: ninguno? Visible, como en no detrás de otra cosa en el orden Z?
Adam Wright

66
como en el rectángulo que se muestra actualmente. Gracias. Reformulado
benzaita

2
Tenga en cuenta que todas las respuestas aquí le darán un falso positivo para los elementos que están fuera del área visible de su elemento padre (eh con desbordamiento oculto) pero dentro del área de la ventana gráfica.
Andy E

1
Hay un millón de respuestas y la mayoría son ridículamente largas. Vea aquí para un dos-liner
leonheess

Respuestas:


347

Actualización: el tiempo avanza y también nuestros navegadores. Esta técnica ya no se recomienda y debe usar la solución de Dan si no necesita admitir la versión de Internet Explorer anterior a la 7.

Solución original (ahora obsoleta):

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

1
La función original publicada tuvo un error. Necesario para guardar el ancho / alto antes de reasignar el ...
Prestaul

15
¿Qué pasa si el elemento vive en un div desplazable y se desplaza fuera de una vista?
amartynov

2
Revise una versión más reciente del script a continuación
Dan

1
También curiosidad por la pregunta de @amartynov. ¿Alguien sabe cómo decir simplemente si un elemento está oculto debido al desbordamiento de un elemento ancestro? Bonificación si esto se puede detectar independientemente de cuán profundamente anidado esté el niño.
Eric Nguyen el

1
@deadManN que recurre a través del DOM es notoriamente lento. Esa es una razón suficiente, pero los proveedores de navegadores también han creado getBoundingClientRectespecíficamente con el propósito de encontrar coordenadas de elementos ... ¿Por qué no lo usaríamos?
Prestaul

1403

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

La solución seleccionada como correcta casi nunca es precisa . Puedes leer más sobre sus errores.


Esta solución se probó en Internet Explorer 7 (y posterior), iOS 5 (y posterior) Safari, Android 2.0 (Eclair) y posterior, BlackBerry, Opera Mobile e Internet Explorer Mobile 9 .


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 dada anteriormente devuelve la respuesta correcta en el momento en que se llama, pero ¿qué pasa con el seguimiento de la visibilidad del elemento como 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); // Internet Explorer 9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

Si realiza modificaciones DOM, pueden cambiar la visibilidad de su elemento, por supuesto.

Pautas y dificultades comunes:

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

Si modifica DOM , puede afectar la visibilidad del elemento. Debe tomar el control sobre eso y llamar handler()manualmente. Desafortunadamente, no tenemos ningún onrepaintevento de navegador cruzado . Por otro lado, eso nos permite hacer optimizaciones y volver a verificar solo las modificaciones DOM que pueden cambiar la visibilidad de un elemento.

Nunca lo use solo dentro de jQuery $ (document) .ready () , porque no hay garantía de que se haya aplicado CSS en este momento. Su código puede funcionar localmente con su CSS en un disco duro, pero una vez que se coloca en un servidor remoto fallará.

Después de DOMContentLoadeddisparar, se aplican estilos , pero las imágenes aún no se cargan . Por lo tanto, deberíamos agregar window.onloadescucha de eventos.

Todavía no podemos ver el evento zoom / pellizco.

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

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

Puede usar la increíble página de características Visibilidad de la API HTML5 si le importa si la pestaña de su página web está activa y visible.

TODO: este método no maneja dos situaciones:


66
Estoy usando esta solución (cuidado con el error tipográfico "botom"). También hay algo a tener en cuenta, cuando el elemento que estamos considerando tendría imágenes. Chrome (al menos) debe esperar a que se cargue la imagen para que tenga el valor exacto del límite delimitador. Parece que Firefox no tiene este "problema"
Claudio

44
¿Funciona cuando tiene habilitado el desplazamiento en un contenedor dentro del cuerpo? Por ejemplo, no funciona aquí: agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). Innerele está presente dentro de un contenedor que tiene habilitado el desplazamiento.
agaase

70
Los cálculos suponen que el elemento es más pequeño que la pantalla. Si tiene elementos altos o anchos, podría ser más preciso de usarreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan

11
Consejo: Para aquellos que intentan implementar esto con jQuery, solo un recordatorio amigable para pasar el objeto DOM HTML (por ejemplo, isElementInViewport(document.getElementById('elem'))) y no el objeto jQuery (por ejemplo, isElementInViewport($("#elem))). El equivalente jQuery es agregar [0]este modo: isElementInViewport($("#elem)[0]).
jiminy

11
el is not defined
M_Willett

176

Actualizar

En los navegadores modernos, es posible que desee consultar la API Intersection Observer que ofrece los siguientes beneficios:

  • Mejor rendimiento que escuchar eventos de desplazamiento
  • Funciona en iframes de dominio cruzado
  • Puede saber 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 la respuesta proporcionada por Dan que podrían hacer que sea un enfoque inadecuado para algunas situaciones. Algunos de estos problemas se señalan en su respuesta cerca del final, que su código dará falsos positivos para elementos que son:

  • Oculto por otro elemento frente al que se está probando
  • Fuera del área visible de un elemento padre o ancestro
  • Un elemento o sus elementos secundarios ocultos mediante el uso de la clippropiedad CSS

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

Prueba fallida, usando 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 || document.documentElement.clientWidth,
        vHeight  = window.innerHeight || document.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))
    );
}

Prueba de aprobación: http://jsfiddle.net/AndyE/cAY8c/

Y el resultado:

Prueba aprobada, usando isElementVisible

Notas adicionales

Sin embargo, este método no está exento de limitaciones. Por ejemplo, un elemento que se está probando con un índice z más bajo que otro elemento en la misma ubicación se identificaría como oculto incluso si el elemento en el frente en realidad no oculta 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 CSSOM y son compatibles con al menos IE 6 y posteriores y la mayoría de los navegadores de escritorio durante mucho tiempo (aunque no de manera perfecta). Consulte Quirksmode sobre estas funciones para obtener más información.

contains()se usa para ver si el elemento devuelto por document.elementFromPoint()es un nodo hijo del elemento que estamos probando para la 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, siendo Firefox 9.0 el último de ellos en agregarlo. Para el soporte anterior de Firefox, revise el historial de esta respuesta.

Si desea probar la visibilidad de más puntos alrededor del elemento, 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 sería muy lento si verificara cada píxel para asegurarse de que fuera 100% visible.


2
¿Querías usar doc.documentElement.clientWidth? ¿Debería ser eso 'document.documentElement' en su lugar? En una nota diferente, este es el único método que también funciona para casos de uso como ocultar el contenido de un elemento para la accesibilidad utilizando la propiedad 'clip' de CSS: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping

@klamping: buena captura, gracias. Lo copié directamente de mi código donde lo estaba usando doccomo alias document. Sí, me gusta pensar en esto como una solución decente para los casos extremos.
Andy E

2
Para mí no está funcionando. Pero inViewport () en la respuesta anterior está funcionando en FF.
Satya Prakash

99
También puede ser beneficioso verificar que el centro del elemento sea visible si tiene esquinas redondeadas o una transformación aplicada, ya que las esquinas delimitadoras pueden no devolver el elemento esperado:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared

2
No funcionó en las entradas para mí (cromo canario 50). No estoy seguro de por qué, ¿quizás esquinas más redondas nativas? Tuve que reducir ligeramente las coordenadas para que funcionara el.contains (efp (rect.left + 1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.bottom-1)) || el.contains (efp (rect.left + 1, rect.bottom-1))
Rayjax

65

Intenté la respuesta de Dan . Sin embargo , el álgebra utilizada para determinar los límites significa que el elemento debe ser tanto ≤ el tamaño de la ventana gráfica como completamente dentro de la ventana gráfica para obtener true, lo que lleva fácilmente a falsos negativos. Si desea determinar si un elemento está en la ventana gráfica , la respuesta de ryanve es cercana, pero el elemento que se está probando debe superponerse a la ventana gráfica, así que intente esto:

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() */;
}

33

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

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

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>

2
isElementPartiallyInViewportTambién es muy útil. Buena esa.
RJ Cuthbertson

Para isElementInViewport (e): su código ni siquiera está cargando imágenes, esta es correcta: la función isElementInViewport (e) {var t = e.getBoundingClientRect (); return t.top> = 0 && t.left> = 0 && t.bottom <= (window.innerHeight || document.documentElement.clientHeight) && t.right <= (window.innerWidth || document.documentElement.clientWidth) }
Arun chauhan

1
@Arun chauhan: Ninguno de mis códigos está cargando imágenes, entonces, ¿por qué debería hacerlo?
Stefan Steiger

1
@targumon: La razón es el soporte de navegadores antiguos.
Stefan Steiger

1
@StefanSteiger según MDN, es compatible desde IE9, por lo que es prácticamente seguro (al menos en mi caso) simplemente usar window.innerHeight directamente. ¡Gracias!
targumon el

28

Vea la fuente de verge , 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
    );

}

Se devuelve truesi alguna parte del elemento está en la ventana gráfica.


27

Mi versión más corta y más rápida:

function isElementOutViewport(el){
    var rect = el.getBoundingClientRect();
    return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}

Y un jsFiddle según sea necesario: https://jsfiddle.net/on1g619L/1/


44
Mi solución es más codiciosa y más rápida, cuando el elemento tiene un píxel en la ventana gráfica, devolverá falso.
Eric Chen

1
Me gusta. Conciso. Puede eliminar los espacios entre el nombre de la función y el paréntesis, y entre paréntesis y llaves, en la primera línea. Nunca me gustaron esos espacios. Tal vez es solo mi editor de texto el que codifica por colores todo lo que hace que sea fácil de leer. function aaa (arg) {declaraciones} Sé que no hace que se ejecute más rápido, sino que se reduce a minificar.
YK

21

Me pareció preocupante que no hubiera una versión jQuery de la funcionalidad disponible. Cuando me encontré con la solución de Dan, descubrí la oportunidad de proporcionar algo para las personas que les gusta programar en el estilo jQuery OO. Es agradable y ágil y funciona como un encanto para mí.

Bada Bing Bada Boom

$.fn.inView = function(){
    if(!this.length) 
        return false;
    var rect = this.get(0).getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );

};

// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
    var all = [];
    this.forEach(function(){
        all.push( $(this).inView() );
    });
    return all.indexOf(false) === -1;
};

// Only the class elements in view
$('.some-class').filter(function(){
    return $(this).inView();
});

// Only the class elements not in view
$('.some-class').filter(function(){
    return !$(this).inView();
});

Uso

$(window).on('scroll',function(){

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});

1
¿Sería suficiente la llave para cerrar la declaración if?
calasyr

1
No pude hacerlo funcionar con múltiples elementos de una clase idéntica.
The Whiz of Oz

1
@TheWhizofOz He actualizado mi respuesta para dar ejemplos de los otros posibles casos de uso que ha mencionado. la mejor de las suertes.
r3wt

10

La nueva API de Intersection Observer aborda esta pregunta muy directamente.

Esta solución necesitará un polyfill ya que Safari, Opera e Internet Explorer aún no lo admiten (el polyfill está incluido en la solución).

En esta solución, hay un cuadro fuera de la vista que es el objetivo (observado). Cuando aparece, el botón en la parte superior del encabezado está oculto. Se muestra una vez que el cuadro abandona la vista.

const buttonToHide = document.querySelector('button');

const hideWhenBoxInView = new IntersectionObserver((entries) => {
  if (entries[0].intersectionRatio <= 0) { // If not in view
    buttonToHide.style.display = "inherit";
  } else {
    buttonToHide.style.display = "none";
  }
});

hideWhenBoxInView.observe(document.getElementById('box'));
header {
  position: fixed;
  top: 0;
  width: 100vw;
  height: 30px;
  background-color: lightgreen;
}

.wrapper {
  position: relative;
  margin-top: 600px;
}

#box {
  position: relative;
  left: 175px;
  width: 150px;
  height: 135px;
  background-color: lightblue;
  border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
  <button>NAVIGATION BUTTON TO HIDE</button>
</header>
  <div class="wrapper">
    <div id="box">
    </div>
  </div>


3
Buena implementación, y de acuerdo con el enlace en esta respuesta , debería funcionar en un safari agregando <!DOCTYPE html>al HTML
Alon Eitan

Tenga en cuenta que IntersectionObserveres una característica experimental (que puede cambiar en el futuro).
Karthik Chintala

2
@KarthikChintala, es compatible con todos los navegadores, excepto IE, y también hay un polyfill disponible.
Randy Casburn

No aborda la pregunta del OP ya que solo detecta cambios : IntersectionObserversolo activa la devolución de llamada después del movimiento del objetivo en relación con la raíz.
Brandon Hill

Cuando se activa un observeevento de llamada , se le informa de inmediato el estado actual de intersección del elemento rastreado. Entonces, de alguna manera, se dirige.
Mesqalito

8

Todas las respuestas que he encontrado aquí solo verifican si el elemento está posicionado dentro de la ventana gráfica actual . Pero eso no significa que sea visible .
¿Qué pasa si el elemento dado está dentro de un div con contenido desbordado y se desplaza fuera de la vista?

Para resolver eso, tendría que verificar si el elemento está contenido por todos los padres.
Mi solución hace exactamente eso:

También le permite especificar cuánto del elemento tiene que ser visible.

Element.prototype.isVisible = function(percentX, percentY){
    var tolerance = 0.01;   //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
    if(percentX == null){
        percentX = 100;
    }
    if(percentY == null){
        percentY = 100;
    }

    var elementRect = this.getBoundingClientRect();
    var parentRects = [];
    var element = this;

    while(element.parentElement != null){
        parentRects.push(element.parentElement.getBoundingClientRect());
        element = element.parentElement;
    }

    var visibleInAllParents = parentRects.every(function(parentRect){
        var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
        var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
        var visiblePercentageX = visiblePixelX / elementRect.width * 100;
        var visiblePercentageY = visiblePixelY / elementRect.height * 100;
        return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
    });
    return visibleInAllParents;
};

Esta solución ignoró el hecho de que los elementos pueden no ser visibles debido a otros hechos, como opacity: 0.

He probado esta solución en Chrome e Internet Explorer 11.


8

Encuentro que la respuesta aceptada aquí es demasiado complicada para la mayoría de los casos de uso. Este código funciona bien (usando jQuery) y diferencia entre elementos totalmente visibles y parcialmente visibles:

var element         = $("#element");
var topOfElement    = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window         = $(window);

$window.bind('scroll', function() {

    var scrollTopPosition   = $window.scrollTop()+$window.height();
    var windowScrollTop     = $window.scrollTop()

    if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
        // Element is partially visible (above viewable area)
        console.log("Element is partially visible (above viewable area)");

    } else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
        // Element is hidden (above viewable area)
        console.log("Element is hidden (above viewable area)");

    } else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
        // Element is hidden (below viewable area)
        console.log("Element is hidden (below viewable area)");

    } else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
        // Element is partially visible (below viewable area)
        console.log("Element is partially visible (below viewable area)");

    } else {
        // Element is completely visible
        console.log("Element is completely visible");
    }
});

Definitivamente debe almacenar $window = $(window)en caché fuera del controlador de desplazamiento.
sam

4

La solución más simple como el soporte de Element.getBoundingClientRect () se ha vuelto perfecta :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}

¿Cómo se comporta esto en los navegadores móviles? La mayoría de ellos tienen errores con respecto a la ventana gráfica, con su encabezado hacia arriba o hacia abajo en el desplazamiento, y un comportamiento diferente cuando aparece el teclado, dependiendo de si es Android o iOS, etc.
Kev

@Kev Debería funcionar bien dependiendo de cuándo llame a este método. Si lo llama y luego cambia el tamaño de la ventana, el resultado obviamente ya no será correcto. Puede llamarlo en cada evento de cambio de tamaño dependiendo del tipo de funcionalidad que desee. Siéntase libre de hacer una pregunta por separado sobre su caso de uso específico y enviarme un ping aquí.
leonheess

3

Creo que esta es una forma más funcional de hacerlo. La respuesta de Dan no funciona en un contexto recursivo.

Esta función resuelve el problema cuando su elemento está dentro de otros divs desplazables probando cualquier nivel recursivamente hasta la etiqueta HTML, y se detiene en el primer falso.

/**
 * fullVisible=true only returns true if the all object rect is visible
 */
function isReallyVisible(el, fullVisible) {
    if ( el.tagName == "HTML" )
            return true;
    var parentRect=el.parentNode.getBoundingClientRect();
    var rect = arguments[2] || el.getBoundingClientRect();
    return (
            ( fullVisible ? rect.top    >= parentRect.top    : rect.bottom > parentRect.top ) &&
            ( fullVisible ? rect.left   >= parentRect.left   : rect.right  > parentRect.left ) &&
            ( fullVisible ? rect.bottom <= parentRect.bottom : rect.top    < parentRect.bottom ) &&
            ( fullVisible ? rect.right  <= parentRect.right  : rect.left   < parentRect.right ) &&
            isReallyVisible(el.parentNode, fullVisible, rect)
    );
};

3

Las respuestas más aceptadas no funcionan al hacer zoom en Google Chrome en Android. En combinación con la respuesta de Dan , para tener en cuenta Chrome en Android, se debe usar visualViewport . El siguiente ejemplo solo tiene en cuenta la verificación vertical y usa jQuery para la altura de la ventana:

var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
    ElTop -= window.visualViewport.offsetTop;
    ElBottom -= window.visualViewport.offsetTop;
    WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);

2

Aquí está mi solución. Funcionará si un elemento está oculto dentro de un contenedor desplazable.

Aquí hay una demostración (intenta cambiar el tamaño de la ventana)

var visibleY = function(el){
    var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
    do {
        rect = el.getBoundingClientRect();
        if (top <= rect.bottom === false)
            return false;
        el = el.parentNode;
    } while (el != document.body);
    // Check it's within the document viewport
    return top <= document.documentElement.clientHeight;
};

Solo necesitaba verificar si está visible en el eje Y (para una función de desplazamiento de carga más registros Ajax).


2

Basado en la solución de dan , tuve la oportunidad de limpiar la implementación para que sea más fácil usarla varias veces en la misma página:

$(function() {

  $(window).on('load resize scroll', function() {
    addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
    addClassToElementInViewport($('.another-thing'), 'animate-thing');
    // 👏 repeat as needed ...
  });

  function addClassToElementInViewport(element, newClass) {
    if (inViewport(element)) {
      element.addClass(newClass);
    }
  }

  function inViewport(element) {
    if (typeof jQuery === "function" && element instanceof jQuery) {
      element = element[0];
    }
    var elementBounds = element.getBoundingClientRect();
    return (
      elementBounds.top >= 0 &&
      elementBounds.left >= 0 &&
      elementBounds.bottom <= $(window).height() &&
      elementBounds.right <= $(window).width()
    );
  }

});

La forma en que lo uso es que cuando el elemento se desplaza a la vista, agrego una clase que desencadena una animación de fotograma clave CSS. Es bastante sencillo y funciona especialmente bien cuando tienes más de 10 cosas para animar condicionalmente en una página.


Definitivamente debe almacenar $window = $(window)en caché fuera del controlador de desplazamiento
Adam Rehal

2

La mayoría de los usos en las respuestas anteriores fallan en estos puntos:

-Cuando cualquier píxel de un elemento es visible, pero no " una esquina ",

-Cuando un elemento es más grande que la vista y centrado ,

-La mayoría de ellos están buscando solo un elemento singular dentro de un documento o ventana .

Bueno, para todos estos problemas tengo una solución y los aspectos positivos son:

-Puedes regresar visiblecuando solo aparece un píxel de cualquier lado y no es una esquina,

-Puedes regresar visiblemientras el elemento sea más grande que la ventana gráfica

-Puedes elegir tu parent elemento puedes dejar que elija automáticamente,

-Trabaja en elementos añadidos dinámicamente también.

Si marca los fragmentos a continuación, verá que la diferencia en el uso overflow-scrolldel contenedor del elemento no causará ningún problema y verá que, a diferencia de otras respuestas aquí, incluso si aparece un píxel desde cualquier lado o cuando un elemento es más grande que la ventana gráfica y estamos viendo el interior píxeles del elemento que aún funciona.

El uso es simple:

// For checking element visibility from any sides
isVisible(element)

// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)

// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice

Una demostración sin la crossSearchAlgorithmcual es útil para elementos más grandes que el elemento de verificación de la ventana de visualización3 píxeles internos para ver:

Verá, cuando está dentro del elemento3 , no puede decir si es visible o no, porque solo estamos verificando si el elemento es visible desde los lados o las esquinas .

Y este incluye el crossSearchAlgorithmque le permite regresar visiblecuando el elemento es más grande que la ventana gráfica:

JSFiddle para jugar: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Este código está hecho para obtener información más precisa si alguna parte del elemento se muestra en la vista o no. Para opciones de rendimiento o solo diapositivas verticales, ¡no use esto! Este código es más efectivo en casos de dibujo.


1

Una mejor solución:

function getViewportSize(w) {
    var w = w || window;
    if(w.innerWidth != null)
        return {w:w.innerWidth, h:w.innerHeight};
    var d = w.document;
    if (document.compatMode == "CSS1Compat") {
        return {
            w: d.documentElement.clientWidth,
            h: d.documentElement.clientHeight
        };
    }
    return { w: d.body.clientWidth, h: d.body.clientWidth };
}


function isViewportVisible(e) {
    var box = e.getBoundingClientRect();
    var height = box.height || (box.bottom - box.top);
    var width = box.width || (box.right - box.left);
    var viewport = getViewportSize();
    if(!height || !width)
        return false;
    if(box.top > viewport.h || box.bottom < 0)
        return false;
    if(box.right < 0 || box.left > viewport.w)
        return false;
    return true;
}

12
Deberías intentar explicar por qué tu versión es mejor. Tal como está, se ve más o menos igual que las otras soluciones.
Andy E

1
Gran solución, tiene un BOX / ScrollContainer y no está usando la VENTANA (solo si no está especificado). Eche un vistazo al código que califica que es una solución más universal (lo estaba buscando mucho)
después

1

Tan simple como puede ser, IMO:

function isVisible(elem) {
  var coords = elem.getBoundingClientRect();
  return Math.abs(coords.top) <= coords.height;
}

0

Aquí hay una función que indica si un elemento está visible en la ventana gráfica actual de un elemento padre :

function inParentViewport(el, pa) {
    if (typeof jQuery === "function"){
        if (el instanceof jQuery)
            el = el[0];
        if (pa instanceof jQuery)
            pa = pa[0];
    }

    var e = el.getBoundingClientRect();
    var p = pa.getBoundingClientRect();

    return (
        e.bottom >= p.top &&
        e.right >= p.left &&
        e.top <= p.bottom &&
        e.left <= p.right
    );
}

0

Esto verifica si un elemento está al menos parcialmente a la vista (dimensión vertical):

function inView(element) {
    var box = element.getBoundingClientRect();
    return inViewBox(box);
}

function inViewBox(box) {
    return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}


function getWindowSize() {
    return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}

0

Tuve la misma pregunta y la resolví usando getBoundingClientRect ().

Este código es completamente 'genérico' y solo tiene que escribirse una vez para que funcione (no tiene que escribirlo para cada elemento que desea saber que está en la ventana gráfica).

Este código solo verifica si está verticalmente en la ventana gráfica, no horizontalmente . En este caso, la variable (matriz) 'elementos' contiene todos los elementos que está verificando que estén verticalmente en la ventana gráfica, así que tome los elementos que desee en cualquier lugar y guárdelos allí.

El 'bucle for', recorre cada elemento y comprueba si está verticalmente en la ventana gráfica. ¡Este código se ejecuta cada vez que el usuario se desplaza! Si getBoudingClientRect (). Top es inferior a 3/4 de la ventana gráfica (el elemento es un cuarto en la ventana gráfica), se registra como 'en la ventana gráfica'.

Como el código es genérico, querrá saber "qué" elemento está en la ventana gráfica. Para descubrirlo, puede determinarlo por atributo personalizado, nombre de nodo, id, nombre de clase y más.

Aquí está mi código (dime si no funciona; se ha probado en Internet Explorer 11, Firefox 40.0.3, Chrome Versión 45.0.2454.85 m, Opera 31.0.1889.174 y Edge con Windows 10, [aún no Safari ]) ...

// Scrolling handlers...
window.onscroll = function(){
  var elements = document.getElementById('whatever').getElementsByClassName('whatever');
  for(var i = 0; i != elements.length; i++)
  {
   if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
      elements[i].getBoundingClientRect().top > 0)
   {
      console.log(elements[i].nodeName + ' ' +
                  elements[i].className + ' ' +
                  elements[i].id +
                  ' is in the viewport; proceed with whatever code you want to do here.');
   }
};

0

Esta es la solución fácil y pequeña que me ha funcionado.

Ejemplo : desea ver si el elemento está visible en el elemento principal que tiene un desplazamiento de desbordamiento.

$(window).on('scroll', function () {

     var container = $('#sidebar');
     var containerHeight = container.height();
     var scrollPosition = $('#row1').offset().top - container.offset().top;

     if (containerHeight < scrollPosition) {
         console.log('not visible');
     } else {
         console.log('visible');
     }
})

0

Todas las respuestas aquí determinan si el elemento está completamente contenido dentro de la ventana gráfica, no solo visible de alguna manera. Por ejemplo, si solo se ve la mitad de una imagen en la parte inferior de la vista, las soluciones aquí fallarán, considerando que "afuera".

Tuve un caso de uso en el que realizo una carga diferida IntersectionObserver, pero debido a las animaciones que ocurren durante el pop-in, no quería observar ninguna imagen que ya estuviera intersectada en la carga de la página. Para hacer eso, utilicé el siguiente código:

const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
        (0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));

Básicamente, esto es verificar si el límite superior o inferior está independientemente en la ventana gráfica. El extremo opuesto puede estar afuera, pero mientras un extremo esté adentro, es "visible" al menos parcialmente.


-1

Uso esta función (solo comprueba si la y está en pantalla, ya que la mayoría de las veces la x no es necesaria)

function elementInViewport(el) {
    var elinfo = {
        "top":el.offsetTop,
        "height":el.offsetHeight,
    };

    if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
        return false;
    } else {
        return true;
    }

}

-3

Para un desafío similar, realmente disfruté esta esencia que expone un polyfill para scrollIntoViewIfNeeded () .

Todo el Kung Fu necesario para responder está dentro de este bloque:

var parent = this.parentNode,
    parentComputedStyle = window.getComputedStyle(parent, null),
    parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
    parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
    overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
    overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
    overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
    overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
    alignWithTop = overTop && !overBottom;

thisse refiere al elemento que desea saber si es, por ejemplo, overTopo overBottomsimplemente debe obtener la deriva ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.