Come posso sapere se un elemento DOM è visibile nella finestra corrente?


950

Esiste un modo efficace per sapere se un elemento DOM (in un documento HTML) è attualmente visibile (appare nella finestra )?

(La domanda si riferisce a Firefox.)


Dipende da cosa intendi per visibile. Se vuoi dire che è attualmente mostrato sulla pagina, data la posizione di scorrimento, puoi calcolarlo in base agli offset y degli elementi e alla posizione di scorrimento corrente.
Roryf,

18
Chiarimento: visibile, come all'interno del rettangolo attualmente visualizzato. Visibile, come in non nascosto o visualizzato: nessuno? Visibile, come in non dietro qualcos'altro nell'ordine Z?
Adam Wright,

6
come all'interno del rettangolo attualmente visualizzato. Grazie. Riformulato.
benzaita,

2
Nota che tutte le risposte qui ti daranno un falso positivo per gli oggetti che sono al di fuori dell'area visibile del loro elemento genitore (eh con overflow nascosto) ma all'interno dell'area della finestra.
Andy E

1
Ci sono un milione di risposte e la maggior parte sono ridicolmente lunghe. Vedi qui per un due-liner
Leonessa

Risposte:


347

Aggiornamento: il tempo avanza e così anche i nostri browser. Questa tecnica non è più consigliata e dovresti usare la soluzione di Dan se non hai bisogno di supportare la versione di Internet Explorer prima del 7.

Soluzione originale (ora obsoleta):

Questo verificherà se l'elemento è interamente visibile nella finestra corrente:

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

È possibile modificarlo semplicemente per determinare se una parte dell'elemento è visibile nella finestra:

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 funzione originale pubblicata ha avuto un errore. Necessario per salvare la larghezza / altezza prima di riassegnare el ...
Prestaul

15
Cosa succede se l'elemento vive in un div scorrevole e scorre fuori da una vista ??
amartynov,

2
Si prega di rivedere una versione più recente dello script di seguito
Dan,

1
Anche curioso della domanda di @ amartynov. Qualcuno sa semplicemente dire se un elemento è nascosto a causa del trabocco di un elemento antenato? Bonus se questo può essere rilevato indipendentemente da quanto sia profondamente nidificato il bambino.
Eric Nguyen,

1
@deadManN che ricorre attraverso il DOM è notoriamente lento. Questo è un motivo sufficiente, ma i fornitori di browser hanno anche creato getBoundingClientRectappositamente per lo scopo di trovare le coordinate degli elementi ... Perché non dovremmo usarlo?
Prestaul,

1402

Ora la maggior parte dei browser supporta il metodo getBoundingClientRect , che è diventato la migliore pratica. L'uso di una vecchia risposta è molto lento , non accurato e presenta diversi bug .

La soluzione selezionata come corretta non è quasi mai precisa . Puoi leggere di più sui suoi bug.


Questa soluzione è stata testata su Internet Explorer 7 (e versioni successive), iOS 5 (e versioni successive) Safari, Android 2.0 (Eclair) e versioni successive, 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() */
    );
}

Come usare:

Puoi essere sicuro che la funzione indicata sopra restituisce la risposta corretta nel momento in cui viene chiamata, ma per quanto riguarda il monitoraggio della visibilità dell'elemento come evento?

Inserisci il seguente codice nella parte inferiore del <body>tag:

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

Se si apportano modifiche al DOM, possono ovviamente cambiare la visibilità del proprio elemento.

Linee guida e insidie ​​comuni:

Forse devi monitorare lo zoom della pagina / il pizzico del dispositivo mobile? jQuery dovrebbe gestire il browser zoom / pizzico incrociato, altrimenti primo o secondo link dovrebbero esserti d'aiuto.

Se modifichi DOM , può influire sulla visibilità dell'elemento. Dovresti prenderne il controllo e chiamare handler()manualmente. Sfortunatamente, non abbiamo onrepainteventi cross-browser . D'altra parte, ciò ci consente di effettuare ottimizzazioni ed eseguire un nuovo controllo solo sulle modifiche del DOM che possono cambiare la visibilità di un elemento.

Mai e poi mai usarlo solo all'interno di jQuery $ (document) .ready () , perché non è stata applicata alcuna garanzia CSS . Il codice può funzionare localmente con i CSS su un disco rigido, ma una volta inserito su un server remoto non funzionerà.

Dopo l' DOMContentLoadedattivazione, vengono applicati gli stili , ma le immagini non vengono ancora caricate . Quindi, dovremmo aggiungerewindow.onload listener di eventi.

Non è ancora possibile rilevare l'evento zoom / pizzico.

L'ultima risorsa potrebbe essere il seguente codice:

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

È possibile utilizzare la caratteristica impressionante pageVisibiliy HTML5 se ti interessa se la scheda con la tua pagina web è attiva e visibile.

TODO: questo metodo non gestisce due situazioni:


6
Sto usando questa soluzione (attenzione però al refuso "botom"). C'è anche qualcosa di cui essere consapevoli, quando l'elemento che stiamo prendendo in considerazione avrebbe delle immagini. Chrome (almeno) deve attendere che l'immagine venga caricata per avere il valore esatto per il boundingRectangle. Sembra che Firefox non abbia questo "problema"
Claudio,

4
Funziona quando lo scorrimento è abilitato in un contenitore all'interno del corpo. Ad esempio, qui non funziona - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele è presente all'interno di un contenitore con lo scorrimento abilitato.
agaase,

70
I calcoli presuppongono che l'elemento sia più piccolo dello schermo. Se hai elementi alti o larghi, potrebbe essere più preciso usarereturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan,

11
Suggerimento: per coloro che cercano di implementarlo con jQuery, basta un semplice promemoria per passare l'oggetto DOM HTML (ad es. isElementInViewport(document.getElementById('elem'))) E non l'oggetto jQuery (ad es isElementInViewport($("#elem)).). Il jQuery equivalente è quello di aggiungere [0]in questo modo: isElementInViewport($("#elem)[0]).
penoso

11
el is not defined
M_Willett,

176

Aggiornare

Nei browser moderni, potresti voler controllare l' API di Intersection Observer che offre i seguenti vantaggi:

  • Prestazioni migliori rispetto all'ascolto di eventi scroll
  • Funziona con iframe tra domini
  • Può dire se un elemento sta ostruendo / intersecando un altro

Intersection Observer sta per diventare uno standard completo ed è già supportato in Chrome 51+, Edge 15+ e Firefox 55+ ed è in fase di sviluppo per Safari. C'è anche un polyfill disponibile.


Risposta precedente

Ci sono alcuni problemi con la risposta fornita da Dan che potrebbero renderlo un approccio inadatto per alcune situazioni. Alcuni di questi problemi sono indicati nella sua risposta in fondo, che il suo codice darà falsi positivi per elementi che sono:

  • Nascosto da un altro elemento di fronte a quello in prova
  • Fuori dall'area visibile di un elemento padre o antenato
  • Un elemento o i suoi figli nascosti utilizzando la clipproprietà CSS

Queste limitazioni sono dimostrate nei seguenti risultati di un semplice test :

Test non riuscito, utilizzando isElementInViewport

La soluzione: isElementVisible()

Ecco una soluzione a questi problemi, con il risultato del test di seguito e una spiegazione di alcune parti del codice.

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

Test di superamento: http://jsfiddle.net/AndyE/cAY8c/

E il risultato:

Test superato, usando isElementVisible

Note aggiuntive

Questo metodo non è privo di limiti, tuttavia. Ad esempio, un elemento in fase di test con un indice z inferiore rispetto a un altro elemento nella stessa posizione verrebbe identificato come nascosto anche se l'elemento davanti non nasconde effettivamente alcuna parte di esso. Tuttavia, questo metodo ha i suoi usi in alcuni casi che la soluzione di Dan non copre.

Entrambi element.getBoundingClientRect()e document.elementFromPoint()fanno parte delle specifiche CSSOM Working Draft e sono supportati da almeno IE 6 e versioni successive e nella maggior parte dei browser desktop per lungo tempo (anche se non perfettamente). Vedi Quirksmode su queste funzioni per ulteriori informazioni.

contains()viene utilizzato per vedere se l'elemento restituito da document.elementFromPoint()è un nodo figlio dell'elemento che stiamo testando per la visibilità. Restituisce anche true se l'elemento restituito è lo stesso elemento. Questo rende il controllo più robusto. È supportato in tutti i principali browser, Firefox 9.0 è l'ultimo ad aggiungerlo. Per il supporto di Firefox precedente, controlla la cronologia di questa risposta.

Se si desidera testare più punti attorno all'elemento per la visibilità ― vale a dire, per assicurarsi che l'elemento non sia coperto da più del, diciamo, 50% ― non ci vorrebbe molto per regolare l'ultima parte della risposta. Tuttavia, tieni presente che probabilmente sarebbe molto lento se controllassi ogni pixel per assicurarti che fosse visibile al 100%.


2
Intendevi usare doc.documentElement.clientWidth? Dovrebbe essere invece "document.documentElement"? In una nota diversa, questo è l'unico metodo che funziona anche per casi d'uso come nascondere il contenuto di un elemento per l'accessibilità usando la proprietà 'clip' CSS: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping

@klamping: buona cattura, grazie. L'avevo copiato dal mio codice dove stavo usando doccome alias document. Sì, mi piace pensare a questa come una soluzione decente per i casi limite.
Andy E

2
Per me non funziona. Ma inViewport () nella risposta precedente funziona in FF.
Satya Prakash,

9
Può anche essere utile verificare che il centro dell'elemento sia visibile se si hanno angoli arrotondati o una trasformazione applicata, poiché gli angoli di delimitazione potrebbero non restituire l'elemento previsto:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared

2
Non ha funzionato sugli input per me (chrome canary 50). Non sai perché, forse angoli arrotondati nativi? Ho dovuto ridurre leggermente le corde per farlo funzionare 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

Ho provato la risposta di Dan . Tuttavia , l'algebra utilizzata per determinare i limiti significa che l'elemento deve essere ≤ la dimensione del viewport e completamente all'interno del viewport per ottenere true, portando facilmente a falsi negativi. Se vuoi determinare se un elemento è nel viewport, la risposta di ryanve è vicina, ma l'elemento in fase di test dovrebbe sovrapporsi al viewport, quindi prova questo:

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

Come servizio pubblico:
la risposta di Dan con i calcoli corretti (l'elemento può essere> finestra, specialmente sugli schermi dei telefoni cellulari) e il test jQuery corretto, oltre ad aggiungere isElementPartiallyInViewport:

A proposito, la differenza tra window.innerWidth e document.documentElement.clientWidth è che clientWidth / clientHeight non include la barra di scorrimento, mentre lo fa window.innerWidth / Height.

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

Test-caso

<!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
isElementPartiallyInViewportè anche molto utile. Ben fatto.
RJ Cuthbertson,

Per isElementInViewport (e): il tuo codice non sta nemmeno caricando immagini, questo è corretto: funzione 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: nessuno del mio codice sta caricando immagini, quindi perché dovrebbe, e la formula è corretta.
Stefan Steiger,

1
@targumon: il motivo è il supporto dei vecchi browser.
Stefan Steiger,

1
@StefanSteiger secondo MDN è supportato da IE9, quindi è praticamente sicuro (almeno nel mio caso) usare semplicemente window.innerHeight direttamente. Grazie!
Targumon,

28

Vedi l'origine di verge , che utilizza getBoundingClientRect . È come:

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

}

Restituisce truese qualsiasi parte dell'elemento è nella finestra.


27

La mia versione più corta e più veloce:

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

E un jsFiddle come richiesto: https://jsfiddle.net/on1g619L/1/


4
Le mie soluzioni sono più avide e veloci, quando l'elemento ha un pixel nel viewport, restituirà false.
Eric Chen,

1
Mi piace. Conciso. È possibile rimuovere gli spazi tra il nome della funzione e la parentesi e tra parentesi e parentesi graffe, sulla prima riga. Non mi sono mai piaciuti quegli spazi. Forse è solo il mio editor di testo che codifica per colore tutto ciò che lo rende ancora facile da leggere. funzione aaa (arg) {istruzioni} So che non lo fa eseguire più velocemente, invece è sotto la minimizzazione.
YK,

21

Ho trovato preoccupante il fatto che non esistesse una versione concentrata di jQuery della funzionalità disponibile. Quando ho trovato la soluzione di Dan ho colto l'opportunità di fornire qualcosa per le persone a cui piace programmare in stile jQuery OO. È bello e scattante e funziona come un fascino per me.

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
La parentesi graffa sarebbe sufficiente per chiudere l'istruzione if?
calasyr,

1
Non sono riuscito a farlo funzionare con più elementi di una classe identica.
The Whiz of Oz,

1
@TheWhizofOz ho aggiornato la mia risposta per fornire esempi degli altri possibili casi d'uso che hai sollevato. buona fortuna.
r3wt

10

La nuova API di Intersection Observer affronta questa domanda in modo molto diretto.

Questa soluzione avrà bisogno di un polyfill poiché Safari, Opera e Internet Explorer non lo supportano ancora (il polyfill è incluso nella soluzione).

In questa soluzione, c'è una scatola fuori dalla vista che è il bersaglio (osservato). Quando viene visualizzato, il pulsante nella parte superiore dell'intestazione è nascosto. Viene visualizzato quando la casella lascia 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
Buona esecuzione, e in base al collegamento in questa risposta si dovrebbe lavorare su Safari, aggiungendo <!DOCTYPE html>al codice HTML
Alon Eitan

Si noti che IntersectionObserverè una funzione sperimentale (che potrebbe cambiare in futuro).
Karthik Chintala,

2
@KarthikChintala - è supportato in tutti i browser tranne IE - e c'è anche un polyfill disponibile.
Randy Casburn,

Non affronta la domanda del PO poiché rileva solo le modifiche : IntersectionObserverattiva solo la richiamata dopo lo spostamento del bersaglio rispetto alla radice.
Brandon Hill,

Quando viene chiamato l' observeevento viene generato immediatamente indicando lo stato di intersezione corrente dell'elemento tracciato. Quindi, in qualche modo - si rivolge.
Mesqalito,

8

Tutte le risposte che ho incontrato qui verificano solo se l'elemento è posizionato all'interno della finestra corrente . Ma ciò non significa che sia visibile .
Che cosa succede se un determinato elemento si trova all'interno di un div con contenuto traboccante e viene fatto scorrere fuori dalla vista?

Per risolverlo, dovresti controllare se l'elemento è contenuto da tutti i genitori.
La mia soluzione fa esattamente questo:

Inoltre, consente di specificare la quantità di elemento che deve essere visibile.

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

Questa soluzione ha ignorato il fatto che gli elementi potrebbero non essere visibili a causa di altri fatti, come opacity: 0.

Ho testato questa soluzione in Chrome e Internet Explorer 11.


8

Trovo che la risposta accettata qui sia eccessivamente complicata per la maggior parte dei casi d'uso. Questo codice fa bene il lavoro (usando jQuery) e distingue tra elementi completamente visibili e parzialmente visibili:

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

Dovresti sicuramente memorizzare $window = $(window)la cache all'esterno del gestore di scorrimento.
sam,

4

La soluzione più semplice come supporto di Element.getBoundingClientRect () è diventata perfetta :

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

Come si comporta sui browser mobili? Molti di loro sono buggy per quanto riguarda il viewport, con la loro intestazione che sale o scende sullo scroll e un comportamento diverso quando appare la tastiera, a seconda che sia Android o iOS, ecc.
Kev

@Kev Dovrebbe funzionare bene a seconda di quando si chiama questo metodo. Se lo chiami e poi ridimensioni la finestra, il risultato potrebbe ovviamente non essere più corretto. Potresti chiamarlo su ogni evento di ridimensionamento a seconda del tipo di funzionalità che desideri. Sentiti libero di fare una domanda separata sul tuo caso d'uso specifico e inviami un ping qui.
leonheess,

3

Penso che questo sia un modo più funzionale per farlo. La risposta di Dan non funziona in un contesto ricorsivo.

Questa funzione risolve il problema quando il tuo elemento si trova all'interno di altri div scorrevoli testando tutti i livelli ricorsivamente fino al tag HTML e si ferma al primo 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

Le risposte più accettate non funzionano quando si esegue lo zoom in Google Chrome su Android. In combinazione con la risposta di Dan , per tenere conto di Chrome su Android, è necessario utilizzare visualViewport . L'esempio seguente tiene conto solo del controllo verticale e utilizza jQuery per l'altezza della finestra:

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

Ecco la mia soluzione Funzionerà se un elemento è nascosto all'interno di un contenitore scorrevole.

Ecco una demo (prova a ridimensionare la finestra in)

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

Avevo solo bisogno di verificare se è visibile nell'asse Y (per una funzionalità Ajax di caricamento di più record).


2

Basandomi sulla soluzione di Dan , ho provato a ripulire l'implementazione in modo che l'utilizzo più volte nella stessa pagina sia più semplice:

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

});

Il modo in cui lo sto usando è che quando l'elemento scorre in vista, sto aggiungendo una classe che attiva un'animazione del keyframe CSS. È piuttosto semplice e funziona particolarmente bene quando hai più di 10 cose da animare condizionatamente su una pagina.


Dovresti assolutamente memorizzare $window = $(window)la cache all'esterno del gestore di scorrimento
Adam Rehal,

2

La maggior parte degli utilizzi nelle risposte precedenti fallisce in questi punti:

-Quando è visibile qualsiasi pixel di un elemento, ma non " un angolo ",

-Quando un elemento è più grande del viewport e centrato ,

-La maggior parte di essi controlla solo un elemento singolare all'interno di un documento o una finestra .

Bene, per tutti questi problemi ho una soluzione e i lati positivi sono:

-Puoi tornare visiblequando viene visualizzato solo un pixel da qualsiasi lato e non è un angolo,

-Puoi comunque tornare visiblementre un elemento più grande del viewport,

-Si può scegliere il tuo parent element o puoi lasciarlo automaticamente scegliere,

- Funziona anche su elementi aggiunti dinamicamente .

Se controlli i frammenti di seguito vedrai che la differenza nell'uso overflow-scrollnel contenitore dell'elemento non causerà alcun problema e vedrai che diversamente dalle altre risposte qui anche se un pixel si presenta da qualsiasi lato o quando un elemento è più grande del viewport e stiamo vedendo interno pixel dell'elemento funziona ancora.

L'uso è semplice:

// 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 dimostrazione senza la crossSearchAlgorithmquale è utile per elementi più grandi di viewport check element3 pixel interni per vedere:

Vedi, quando sei all'interno di element3 non riesce a capire se è visibile o meno, perché stiamo solo verificando se l'elemento è visibile dai lati o dagli angoli .

E questo include crossSearchAlgorithmche ti consente di tornare ancora visiblequando l'elemento è più grande del viewport:

JSFiddle per giocare con: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Questo codice viene creato per informazioni più precise se una parte dell'elemento viene mostrata o meno nella vista. Per le opzioni di performance o solo diapositive verticali, non usare questo! Questo codice è più efficace nei casi di disegno.


1

Una soluzione migliore:

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
Dovresti provare a spiegare perché la tua versione è migliore. Allo stato attuale, sembra più o meno lo stesso delle altre soluzioni.
Andy E

1
Ottima soluzione ha un BOX / ScrollContainer e non utilizza WINDOW (solo se non specificato). Dai un'occhiata al codice che valuta che è una soluzione più universale (
cercavo

1

Il più semplice possibile, IMO:

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

0

Ecco una funzione che dice se un elemento è visibile nella finestra corrente di un elemento genitore :

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

Questo controlla se un elemento è almeno parzialmente visibile (dimensione verticale):

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

Ho avuto la stessa domanda e l'ho capito usando getBoundingClientRect ().

Questo codice è completamente "generico" e deve essere scritto solo una volta perché funzioni (non è necessario scriverlo per ogni elemento che si desidera sapere sia nella finestra).

Questo codice controlla solo se è verticale nella finestra, non in orizzontale . In questo caso, gli 'elementi' variabili (array) contengono tutti gli elementi che si sta controllando per essere posizionati verticalmente nella finestra, quindi afferrare tutti gli elementi desiderati ovunque e archiviarli lì.

Il 'ciclo for', scorre attraverso ogni elemento e controlla se è verticale nella finestra. Questo codice viene eseguito ogni volta che l'utente scorre! Se getBoudingClientRect (). Top è inferiore a 3/4 del riquadro di visualizzazione (l'elemento è un quarto nel riquadro di visualizzazione), viene registrato come "nel riquadro di visualizzazione".

Poiché il codice è generico, vorrai sapere quale "elemento" è presente nella finestra. Per scoprirlo, puoi determinarlo in base all'attributo personalizzato, al nome nodo, all'ID, al nome della classe e altro.

Ecco il mio codice (dimmi se non funziona; è stato testato in Internet Explorer 11, Firefox 40.0.3, versione Chrome 45.0.2454.85 m, Opera 31.0.1889.174 e Edge con Windows 10, [non ancora 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

Questa è la soluzione semplice e piccola che ha funzionato per me.

Esempio : vuoi vedere se l'elemento è visibile nell'elemento genitore con scorrimento di overflow.

$(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

Tutte le risposte qui determinano se l'elemento è completamente contenuto nel viewport, non solo in qualche modo visibile. Ad esempio, se solo la metà di un'immagine è visibile nella parte inferiore della vista, le soluzioni qui falliranno, considerando che "all'esterno".

Ho avuto un caso d'uso in cui sto eseguendo il caricamento lento tramite IntersectionObserver, ma a causa delle animazioni che si verificano durante il pop-in, non volevo osservare alcuna immagine che fosse già intersecata al caricamento della pagina. Per fare ciò, ho usato il seguente codice:

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

Questo sta fondamentalmente verificando se il limite superiore o inferiore è indipendente nella finestra. L'estremità opposta può essere esterna, ma fintanto che un'estremità è dentro, è "visibile" almeno parzialmente.


-1

Uso questa funzione (controlla solo se y è presente sullo schermo poiché la maggior parte delle volte non è necessaria la x)

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

Per una sfida simile, mi è davvero piaciuta questa sintesi che espone un polyfill per scrollIntoViewIfNeeded () .

Tutto il Kung Fu necessario per rispondere è all'interno di questo blocco:

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;

thissi riferisce all'elemento che si desidera sapere se lo è, ad esempio, overTopoppure overBottom- si dovrebbe semplicemente ottenere la deriva ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.