Comment savoir si un élément DOM est visible dans la fenêtre courante?

950

Existe-t-il un moyen efficace de savoir si un élément DOM (dans un document HTML) est actuellement visible (apparaît dans la fenêtre )?

(La question concerne Firefox.)

benzaita
la source
Cela dépend de ce que vous entendez par visible. Si vous voulez dire s'il est actuellement affiché sur la page, compte tenu de la position de défilement, vous pouvez le calculer en fonction des éléments y offset et de la position de défilement actuelle.
roryf
18
Clarification: visible, comme dans le rectangle actuellement affiché. Visible, comme dans non masqué ou affiché: aucun? Visible, comme pas derrière quelque chose d'autre dans l'ordre Z?
Adam Wright
6
comme dans le rectangle actuellement affiché. Merci. Reformulé.
benzaita
2
Notez que toutes les réponses ici vous donneront un faux positif pour les éléments qui se trouvent en dehors de la zone visible de leur élément parent (eh avec débordement masqué) mais à l'intérieur de la zone de la fenêtre d'affichage.
Andy E
1
Il y a un million de réponses et la plupart sont ridiculement longues. Voir ici pour un deux
lignes

Réponses:

347

Mise à jour: le temps passe et nos navigateurs aussi. Cette technique n'est plus recommandée et vous devez utiliser la solution de Dan si vous n'avez pas besoin de prendre en charge la version d'Internet Explorer avant 7.

Solution originale (désormais obsolète):

Cela vérifiera si l'élément est entièrement visible dans la fenêtre courante:

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

Vous pouvez modifier cela simplement pour déterminer si une partie de l'élément est visible dans la fenêtre:

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
  );
}
Prestaul
la source
1
La fonction d'origine publiée avait une erreur. Nécessaire pour enregistrer la largeur / hauteur avant de réaffecter el ...
Prestaul
15
Que se passe-t-il si l'élément vit dans un div défilable et défile hors d'une vue ??
amartynov
2
Veuillez consulter une version plus récente du script ci
Dan
1
Egalement curieux de la question de @ amartynov. Quelqu'un sait-il simplement dire si un élément est caché en raison du débordement d'un élément ancêtre? Bonus si cela peut être détecté quelle que soit la profondeur de l'emboîtement de l'enfant.
Eric Nguyen
1
@deadManN récursif via le DOM est notoirement lent. C'est une raison suffisante, mais les fournisseurs de navigateurs ont également créé getBoundingClientRectspécifiquement pour trouver des coordonnées d'élément ... Pourquoi ne l'utiliserions-nous pas?
Prestaul
1403

Désormais, la plupart des navigateurs prennent en charge la méthode getBoundingClientRect , qui est devenue la meilleure pratique. L'utilisation d'une ancienne réponse est très lente , imprécise et comporte plusieurs bogues .

La solution choisie comme correcte n'est presque jamais précise . Vous pouvez en savoir plus sur ses bugs.


Cette solution a été testée sur Internet Explorer 7 (et versions ultérieures), iOS 5 (et versions ultérieures) Safari, Android 2.0 (Eclair) et versions ultérieures, BlackBerry, Opera Mobile et 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() */
    );
}

Comment utiliser:

Vous pouvez être sûr que la fonction donnée ci-dessus renvoie une réponse correcte au moment où elle est appelée, mais qu'en est-il du suivi de la visibilité de l'élément en tant qu'événement?

Placez le code suivant au bas de votre <body>balise:

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 vous faites des modifications DOM, elles peuvent bien sûr changer la visibilité de votre élément.

Lignes directrices et pièges courants:

Peut-être que vous devez suivre le zoom de la page / pincer l'appareil mobile? jQuery devrait gérer le navigateur croisé zoom / pincement , sinon le premier ou le deuxième lien devrait vous aider.

Si vous modifiez DOM , cela peut affecter la visibilité de l'élément. Vous devez prendre le contrôle de cela et appeler handler()manuellement. Malheureusement, nous n'avons aucun onrepaintévénement multi-navigateur . D'autre part, cela nous permet de faire des optimisations et d'effectuer une nouvelle vérification uniquement sur les modifications DOM qui peuvent changer la visibilité d'un élément.

Ne l' utilisez jamais dans jQuery $ (document) .ready () uniquement, car il n'y a aucune garantie CSS a été appliquée en ce moment. Votre code peut fonctionner localement avec votre CSS sur un disque dur, mais une fois placé sur un serveur distant, il échouera.

Après le DOMContentLoadeddéclenchement, les styles sont appliqués , mais les images ne sont pas encore chargées . Donc, nous devons ajouter window.onloadun écouteur d'événements.

Nous ne pouvons pas encore capturer l'événement de zoom / pincement.

Le dernier recours pourrait être le code suivant:

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

Vous pouvez utiliser la page de fonctionnalités impressionnante Visibilité de l'API HTML5 si vous vous souciez si l'onglet avec votre page Web est actif et visible.

TODO: cette méthode ne gère pas deux situations:

Dan
la source
6
J'utilise cette solution (attention à la faute de frappe "au fond", cependant). Il y a aussi quelque chose à savoir, lorsque l'élément que nous considérons contient des images. Chrome (au moins) doit attendre que l'image soit chargée pour avoir la valeur exacte du boundingRectangle. Semble que Firefox n'a pas ce "problème"
Claudio
4
Cela fonctionne-t-il lorsque le défilement est activé dans un conteneur à l'intérieur du corps? Par exemple, cela ne fonctionne pas ici - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele est présent à l'intérieur d'un conteneur dont le défilement est activé.
agaase
70
Les calculs supposent que l'élément est plus petit que l'écran. Si vous avez des éléments hauts ou larges, il pourrait être plus précis à utiliserreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan
11
Astuce: Pour ceux qui tentent de l'implémenter avec jQuery, juste un rappel convivial pour passer l'objet DOM HTML (par exemple, isElementInViewport(document.getElementById('elem'))) et non l'objet jQuery (par exemple isElementInViewport($("#elem))). L'équivalent jQuery est d'ajouter [0]comme ceci: isElementInViewport($("#elem)[0]).
jiminy
11
el is not defined
M_Willett
176

Mise à jour

Dans les navigateurs modernes, vous souhaiterez peut-être consulter l' API Intersection Observer qui offre les avantages suivants:

  • Meilleures performances que l'écoute des événements de défilement
  • Fonctionne dans les iframes interdomaines
  • Peut dire si un élément obstrue / recoupe un autre

Intersection Observer est en passe d'être une norme à part entière et est déjà pris en charge dans Chrome 51+, Edge 15+ et Firefox 55+ et est en cours de développement pour Safari. Il existe également un polyfill disponible.


Réponse précédente

Il y a quelques problèmes avec la réponse fournie par Dan qui pourraient en faire une approche inappropriée pour certaines situations. Certains de ces problèmes sont signalés dans sa réponse vers le bas, que son code donnera des faux positifs pour les éléments qui sont:

  • Caché par un autre élément devant celui testé
  • En dehors de la zone visible d'un élément parent ou ancêtre
  • Un élément ou ses enfants masqués à l'aide de la clippropriété CSS

Ces limitations sont démontrées dans les résultats suivants d'un test simple :

Échec du test, en utilisant isElementInViewport

La solution: isElementVisible()

Voici une solution à ces problèmes, avec le résultat du test ci-dessous et une explication de certaines parties du code.

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 de réussite : http://jsfiddle.net/AndyE/cAY8c/

Et le résultat:

Test réussi, en utilisant isElementVisible

Notes complémentaires

Cette méthode n'est cependant pas sans limites. Par exemple, un élément testé avec un z-index inférieur à celui d'un autre élément au même emplacement serait identifié comme masqué même si l'élément en avant n'en cache aucune partie. Pourtant, cette méthode a ses utilisations dans certains cas que la solution de Dan ne couvre pas.

Les deux element.getBoundingClientRect()et document.elementFromPoint()font partie de la spécification CSSOM Working Draft et sont pris en charge dans au moins IE 6 et versions ultérieures et la plupart des navigateurs de bureau pendant une longue période (quoique pas parfaitement). Voir Quirksmode sur ces fonctions pour plus d'informations.

contains()est utilisé pour voir si l'élément renvoyé par document.elementFromPoint()est un nœud enfant de l'élément que nous testons pour la visibilité. Elle renvoie également true si l'élément renvoyé est le même élément. Cela rend simplement le contrôle plus robuste. Il est pris en charge dans tous les principaux navigateurs, Firefox 9.0 étant le dernier à l'ajouter. Pour une prise en charge plus ancienne de Firefox, consultez l'historique de cette réponse.

Si vous voulez tester plus de points autour de l'élément pour la visibilité, c'est-à-dire pour vous assurer que l'élément n'est pas couvert par plus de, disons, 50%, il ne faudrait pas grand-chose pour ajuster la dernière partie de la réponse. Cependant, sachez que ce serait probablement très lent si vous vérifiiez chaque pixel pour vous assurer qu'il était 100% visible.

Andy E
la source
2
Vouliez-vous utiliser doc.documentElement.clientWidth? Cela devrait-il plutôt être «document.documentElement»? Sur une note différente, c'est la seule méthode qui fonctionne également pour les cas d'utilisation comme masquer le contenu d'un élément pour l'accessibilité en utilisant la propriété CSS 'clip': snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping
@klamping: bonne prise, merci. Je l'avais copié directement dans mon code où je l'utilisais doccomme alias document. Oui, j'aime à penser à cela comme une solution décente pour les cas marginaux.
Andy E
2
Pour moi, cela ne fonctionne pas. Mais inViewport () dans la réponse précédente fonctionne dans FF.
Satya Prakash
9
Il peut également être utile de vérifier que le centre de l'élément est visible si vous avez des coins arrondis ou si une transformation est appliquée, car les coins englobants peuvent ne pas renvoyer l'élément attendu:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared
2
Ne fonctionnait pas sur les entrées pour moi (chrome canary 50). Vous ne savez pas pourquoi, peut-être des coins ronds natifs? J'ai dû réduire légèrement les coords pour le faire fonctionner 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

J'ai essayé la réponse de Dan . Cependant , l'algèbre utilisée pour déterminer les limites signifie que l'élément doit être à la fois ≤ la taille de la fenêtre d'affichage et complètement à l'intérieur de la fenêtre d'affichage true, ce qui conduit facilement à de faux négatifs. Si vous voulez déterminer si un élément se trouve dans la fenêtre, la réponse de ryanve est proche, mais l'élément testé doit chevaucher la fenêtre, alors essayez ceci:

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() */;
}
Walf
la source
33

En tant que service public:
la réponse de Dan avec les calculs corrects (l'élément peut être> window, en particulier sur les écrans de téléphones mobiles), et les tests jQuery corrects, ainsi que l'ajout de isElementPartiallyInViewport:

Soit dit en passant, la différence entre window.innerWidth et document.documentElement.clientWidth est que clientWidth / clientHeight n'inclut pas la barre de défilement, contrairement à 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);
}

Cas de test

<!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>
Stefan Steiger
la source
2
isElementPartiallyInViewportest également très utile. Joli.
RJ Cuthbertson,
Pour isElementInViewport (e): Votre code ne charge même pas les images, celui-ci est correct: fonction 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: Aucun de mon code ne charge des images, alors pourquoi devrait-il le faire, et la formule est correcte.
Stefan Steiger
1
@targumon: La raison en est la prise en charge des anciens navigateurs.
Stefan Steiger
1
@StefanSteiger selon MDN, il est pris en charge depuis IE9, il est donc pratiquement sûr (au moins dans mon cas) d'utiliser simplement window.innerHeight directement. Merci!
targumon
28

Voir la source de verge , qui utilise getBoundingClientRect . C'est comme:

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

}

Elle renvoie truesi une partie de l'élément se trouve dans la fenêtre.

ryanve
la source
27

Ma version plus courte et plus rapide:

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

Et un jsFiddle au besoin: https://jsfiddle.net/on1g619L/1/

Eric Chen
la source
4
Ma solution est plus gourmande et plus rapide, lorsque l'élément a un pixel dans la fenêtre, il retournera faux.
Eric Chen
1
Je l'aime. Concis. Vous pouvez supprimer les espaces entre le nom de la fonction et les parenthèses, et entre les parenthèses et les accolades, sur la première ligne. Je n'ai jamais aimé ces espaces. C'est peut-être juste mon éditeur de texte qui code tout ce qui le rend encore plus facile à lire. function aaa (arg) {instructions} Je sais que cela ne le fait pas s'exécuter plus rapidement, tombe sous la minification à la place.
YK
21

J'ai trouvé troublant qu'il n'y ait pas de version jQuery de la fonctionnalité disponible. Lorsque j'ai découvert la solution de Dan, j'ai saisi l'opportunité de fournir quelque chose aux gens qui aiment programmer dans le style jQuery OO. C'est agréable et accrocheur et fonctionne comme un charme pour moi.

Boom Bada Bing Bada

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

Usage

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

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});
r3wt
la source
1
L'attelle bouclée serait-elle suffisante pour fermer la déclaration if?
calasyr
1
Je ne pouvais pas le faire fonctionner avec plusieurs éléments d'une classe identique.
The Whiz of Oz
1
@TheWhizofOz j'ai mis à jour ma réponse pour donner des exemples des autres cas d'utilisation possibles que vous avez évoqués. bonne chance.
r3wt
10

La nouvelle API Intersection Observer répond très directement à cette question.

Cette solution aura besoin d'un polyfill car Safari, Opera et Internet Explorer ne le prennent pas encore en charge (le polyfill est inclus dans la solution).

Dans cette solution, il y a une boîte hors de vue qui est la cible (observée). Lorsqu'il apparaît, le bouton en haut de l'en-tête est masqué. Il s'affiche une fois que la boîte quitte la vue.

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>

Randy Casburn
la source
3
Bonne implémentation, et selon le lien dans cette réponse, il devrait fonctionner sur safari en ajoutant <!DOCTYPE html>au HTML
Alon Eitan
Notez qu'il IntersectionObservers'agit d'une fonctionnalité expérimentale (qui pourrait changer à l'avenir).
Karthik Chintala
2
@KarthikChintala - il est pris en charge dans tous les navigateurs sauf IE - et il existe également un polyfill disponible.
Randy Casburn
Ne répond pas à la question de l'OP car ne détecte que les changements : IntersectionObserverne déclenche que le rappel après le mouvement de la cible par rapport à la racine.
Brandon Hill
Lorsque l'appel observeest déclenché, un événement vous informe immédiatement de l'état actuel de l'intersection de l'élément suivi. Donc, d'une certaine manière - cela répond.
Mesqalito
8

Toutes les réponses que j'ai rencontrées ici vérifient uniquement si l'élément est positionné à l'intérieur de la fenêtre courante . Mais cela ne signifie pas qu'il est visible .
Que se passe-t-il si l'élément donné se trouve dans une div avec un contenu débordant et qu'il est déplacé hors de la vue?

Pour résoudre ce problème, vous devez vérifier si l'élément est contenu par tous les parents.
Ma solution fait exactement cela:

Il vous permet également de spécifier la quantité de l'élément qui doit être 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;
};

Cette solution a ignoré le fait que des éléments peuvent ne pas être visibles en raison d'autres faits, comme opacity: 0.

J'ai testé cette solution dans Chrome et Internet Explorer 11.

Domysee
la source
8

Je trouve que la réponse acceptée ici est trop compliquée pour la plupart des cas d'utilisation. Ce code fait bien le travail (en utilisant jQuery) et différencie les éléments entièrement visibles et partiellement 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");
    }
});
Adam Rehal
la source
Vous devez absolument mettre $window = $(window)en cache en dehors du gestionnaire de défilement.
sam
4

La solution la plus simple comme le support de Element.getBoundingClientRect () est devenue parfaite :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}
solitude
la source
Comment cela se comporte-t-il sur les navigateurs mobiles? La plupart d'entre eux sont bogués en ce qui concerne la fenêtre d'affichage, avec leur en-tête montant ou descendant sur le défilement, et un comportement différent lorsque le clavier apparaît, selon qu'il est Android ou iOS, etc.
Kev
@Kev devrait fonctionner très bien selon le moment où vous appelez cette méthode. Si vous l'appelez et redimensionnez ensuite la fenêtre, le résultat peut évidemment ne plus être correct. Vous pouvez l'appeler à chaque événement de redimensionnement en fonction du type de fonctionnalité que vous souhaitez. N'hésitez pas à poser une question distincte sur votre cas d'utilisation spécifique et envoyez-moi un ping ici.
leonheess le
3

Je pense que c'est une façon plus fonctionnelle de le faire. La réponse de Dan ne fonctionne pas dans un contexte récursif.

Cette fonction résout le problème lorsque votre élément se trouve dans d'autres divs défilables en testant tous les niveaux de manière récursive jusqu'à la balise HTML et s'arrête au premier faux.

/**
 * 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)
    );
};
tonne
la source
3

Les réponses les plus acceptées ne fonctionnent pas lors d'un zoom dans Google Chrome sur Android. En combinaison avec la réponse de Dan , pour tenir compte de Chrome sur Android, visualViewport doit être utilisé. L'exemple suivant ne prend en compte que la vérification verticale et utilise jQuery pour la hauteur de la fenêtre:

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);
Dakusan
la source
2

Voici ma solution. Cela fonctionnera si un élément est caché dans un conteneur déroulant.

Voici une démo (essayez de redimensionner la fenêtre pour)

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

Je n'avais besoin que de vérifier si elle était visible sur l'axe Y (pour une fonction de chargement-plus-d'enregistrements Ajax).

Allié
la source
2

Basé sur la solution de dan , j'ai essayé de nettoyer l'implémentation afin de pouvoir l'utiliser plusieurs fois sur la même page:

$(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 façon dont je l'utilise est que lorsque l'élément défile dans la vue, j'ajoute une classe qui déclenche une animation d'images clés CSS. C'est assez simple et fonctionne particulièrement bien lorsque vous avez plus de 10 choses à animer conditionnellement sur une page.

Pirijan
la source
Vous devez absolument mettre $window = $(window)en cache en dehors du gestionnaire de défilement
Adam Rehal
2

La plupart des utilisations dans les réponses précédentes échouent à ces points:

-Lorsque n'importe quel pixel d'un élément est visible, mais pas " un coin ",

-Lorsqu'un élément est plus grand que la fenêtre et centré ,

-La plupart d'entre eux recherchent uniquement un élément singulier dans un document ou une fenêtre .

Eh bien, pour tous ces problèmes, j'ai une solution et les côtés positifs sont:

-Vous pouvez revenir visiblequand seul un pixel de n'importe quel côté apparaît et n'est pas un coin,

-Vous pouvez toujours revenir visiblealors que l'élément est plus grand que la fenêtre,

-Vous pouvez choisir votre parent element ou vous pouvez le laisser choisir automatiquement,

-Fonctionne également sur les éléments ajoutés dynamiquement .

Si vous cochez les extraits ci-dessous, vous verrez la différence d'utilisation overflow-scrolldans le conteneur de l'élément ne causera aucun problème et voyez que contrairement aux autres réponses ici même si un pixel apparaît de n'importe quel côté ou lorsqu'un élément est plus grand que la fenêtre et que nous voyons intérieur pixels de l'élément, il fonctionne toujours.

L'utilisation est 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

Une démonstration sans crossSearchAlgorithmlaquelle est utile pour des éléments plus grands que la fenêtre de visualisation, vérifiez les pixels intérieurs de element3 pour voir:

Vous voyez, lorsque vous êtes à l'intérieur de l'élément3, il ne parvient pas à dire s'il est visible ou non, car nous vérifions uniquement si l'élément est visible depuis les côtés ou les coins .

Et celui-ci comprend crossSearchAlgorithmce qui vous permet de revenir visiblequand l'élément est plus grand que la fenêtre:

JSFiddle pour jouer avec: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Ce code est fait pour des informations plus précises si une partie de l'élément est affichée dans la vue ou non. Pour les options de performances ou uniquement les diapositives verticales, ne l'utilisez pas! Ce code est plus efficace pour dessiner des cas.

Berker Yüceer
la source
1

Une meilleure solution:

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;
}
rainyjune
la source
12
Vous devriez essayer d'expliquer pourquoi votre version est meilleure. En l'état, elle ressemble plus ou moins aux autres solutions.
Andy E
1
Excellente solution, il a un BOX / ScrollContainer et n'utilise pas la FENÊTRE (uniquement si elle n'est pas spécifiée). Jetez un coup d'œil au code plutôt qu'au taux c'est une solution plus universelle (cherchait beaucoup)
teter
1

Aussi simple que possible, l'OMI:

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

Voici une fonction qui indique si un élément est visible dans la fenêtre courante d'un élément parent :

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
    );
}
ssten
la source
0

Ceci vérifie si un élément est au moins partiellement visible (dimension 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}
}
Lumic
la source
0

J'avais la même question et je l'ai trouvée en utilisant getBoundingClientRect ().

Ce code est complètement «générique» et ne doit être écrit qu'une seule fois pour qu'il fonctionne (vous n'avez pas besoin de l'écrire pour chaque élément que vous voulez savoir se trouve dans la fenêtre).

Ce code vérifie uniquement s'il est vertical dans la fenêtre et non horizontalement . Dans ce cas, la variable (tableau) 'elements' contient tous les éléments que vous vérifiez pour être verticalement dans la fenêtre, alors prenez tous les éléments que vous voulez n'importe où et stockez-les là.

La boucle "for" parcourt chaque élément et vérifie s'il se trouve verticalement dans la fenêtre. Ce code s'exécute à chaque fois que l'utilisateur défile! Si le getBoudingClientRect (). Top est inférieur aux 3/4 de la fenêtre d'affichage (l'élément est un quart dans la fenêtre d'affichage), il s'enregistre comme «dans la fenêtre d'affichage».

Comme le code est générique, vous voudrez savoir quel élément se trouve dans la fenêtre. Pour le découvrir, vous pouvez le déterminer par attribut personnalisé, nom de noeud, id, nom de classe, etc.

Voici mon code (dites-moi si cela ne fonctionne pas; il a été testé dans Internet Explorer 11, Firefox 40.0.3, Chrome Version 45.0.2454.85 m, Opera 31.0.1889.174 et Edge avec Windows 10, [pas encore 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.');
   }
};
www139
la source
0

C'est la solution simple et petite qui a fonctionné pour moi.

Exemple : vous voulez voir si l'élément est visible dans l'élément parent qui a un défilement de débordement.

$(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');
     }
})
Stevan Tosic
la source
0

Toutes les réponses ici déterminent si l'élément est entièrement contenu dans la fenêtre, pas seulement visible d'une manière ou d'une autre. Par exemple, si seulement la moitié d'une image est visible en bas de la vue, les solutions ici échoueront, considérant que "l'extérieur".

J'ai eu un cas d'utilisation où je fais du chargement paresseux via IntersectionObserver, mais en raison des animations qui se produisent pendant la pop-in, je ne voulais pas observer d'images qui étaient déjà coupées lors du chargement de la page. Pour ce faire, j'ai utilisé le code suivant:

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

Il s'agit essentiellement de vérifier si la limite supérieure ou inférieure se trouve indépendamment dans la fenêtre. L'extrémité opposée peut être à l'extérieur, mais tant qu'une extrémité est à l'intérieur, elle est "visible" au moins partiellement.

Chris Pratt
la source
-1

J'utilise cette fonction (elle vérifie uniquement si le y est en écran car la plupart du temps le x n'est pas nécessaire)

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

}
Sander Jonk
la source
-3

Pour un défi similaire, j'ai vraiment apprécié cet essentiel qui expose un polyfill pour scrollIntoViewIfNeeded () .

Tout le Kung Fu nécessaire pour répondre est dans ce bloc:

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;

thisfait référence à l'élément que vous voulez savoir s'il s'agit, par exemple, overTopou overBottom- vous devriez juste obtenir la dérive ...

Philzen
la source