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


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

(La question concerne Firefox)


747
2017-09-23 21:24


origine


Réponses:


Mettre à jour: Le temps passe et nos navigateurs aussi. Cette technique n'est plus recommandée et vous devriez utiliser la solution de @ Dan ci-dessous (https://stackoverflow.com/a/7557433/5628) si vous n'avez pas besoin de prendre en charge IE <7.

Solution originale (maintenant périmée):

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

286
2017-09-24 02:40



À présent la plupart des navigateurs soutien getBoundingClientRect méthode, qui est devenue la meilleure pratique. En utilisant une vieille réponse est très lent, pas précis et a plusieurs bugs.

La solution sélectionnée comme correcte est presque jamais précis. Vous pouvez Lire la suite à propos de ses bugs.


Cette solution a été testée sur IE7 +, iOS5 + Safari, Android2 +, Blackberry, Opera Mobile et IE Mobile dix.


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 de l'appel, mais qu'en est-il de la visibilité de l'élément en tant qu'événement?

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

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

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


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

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

Si 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 avez besoin de suivre la page zoom / pincée de l'appareil mobile? jQuery devrait gérer zoom / pincer navigateur croisé, sinon premier ou seconde Le lien devrait vous aider.

Si vous modifier DOMCela peut affecter la visibilité de l'élément. Vous devriez prendre le contrôle sur cela et appeler handler() manuellement. Malheureusement, nous n'avons pas de navigateur croisé onrepaint un événement. D'un autre côté, cela nous permet de faire des optimisations et d'effectuer une nouvelle vérification uniquement sur les modifications DOM qui peuvent changer la visibilité des éléments.

Plus jamais l'utiliser à l'intérieur de jQuery $ (document) .ready () seulement parceque il n'y a aucune garantie CSS a été appliquée à ce moment. Votre code peut fonctionner localement avec votre CSS sur le disque dur, mais une fois mis sur le serveur distant, il échouera.

Après DOMContentLoaded est viré, les styles sont appliqués, mais les images ne sont pas encore chargées. Donc, nous devrions ajouter window.onload écouteur d'événement.

Nous ne pouvons pas encore voir l'événement zoom / pincée.

Le dernier recours pourrait être le code suivant:

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

Vous pouvez utiliser une fonctionnalité impressionnante pageVisibiliy API HTML5 si vous tenez à savoir si l'onglet avec votre page Web est actif et visible.

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


1163
2018-03-04 14:18



Mettre à jour

Dans les navigateurs modernes, vous pouvez consulter le Intersection Observer API qui fournit les avantages suivants:

  • Meilleure performance que l'écoute des événements de défilement
  • Fonctionne dans les iframes inter-domaines
  • Peut dire si un élément obstrue / croise un autre

Intersection Observer est en passe d'être un standard à part entière et est déjà supporté par Chrome 51+, Edge 15+ et Firefox 55+ et est en cours de développement pour Safari. Il y a aussi un polyfill disponible.


Réponse précédente

Il y a quelques problèmes avec le réponse fournie par Dan cela pourrait en faire une approche inappropriée pour certaines situations. Certaines de ces questions sont soulignées dans sa réponse près du bas, que son code donnera des faux positifs pour les éléments qui sont:

  • Caché par un autre élément en face de celui qui est testé
  • En dehors de la zone visible d'un élément parent ou ancêtre
  • Un élément ou ses enfants cachés en utilisant le CSS clip propriété

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

Failed test, using 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 || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

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

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

Test de passage:  http://jsfiddle.net/AndyE/cAY8c/

Et le résultat:

Passed test, using isElementVisible

Notes complémentaires

Cette méthode n'est pas sans ses propres limites, cependant. Par exemple, un élément testé avec un indice z plus faible qu'un autre élément au même endroit serait identifié comme caché 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.

Tous les deux element.getBoundingClientRect() et document.elementFromPoint() font partie de la spécification du projet de travail CSSOM et sont pris en charge dans au moins IE 6 et plus tard et plus navigateurs de bureau pendant une longue période (mais pas parfaitement). Voir Quirksmode sur ces fonctions pour plus d'informations.

contains() est utilisé pour voir si l'élément retourné par document.elementFromPoint() est un noeud enfant de l'élément que nous testons pour la visibilité. Il retourne aussi vrai si l'élément retourné est le même élément. Cela rend juste le contrôle plus robuste. Il est pris en charge dans tous les principaux navigateurs, Firefox 9.0 étant le dernier à l'ajouter. Pour un support Firefox plus ancien, vérifiez 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 de plus de 50%, il n'en faudra pas beaucoup 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 visible à 100%.


120
2018-04-29 02:36



j'ai essayé La réponse de Dan  mais l'algèbre utilisée pour déterminer les limites est incorrecte. La réponse de Ryan est plus proche, mais l'élément testé doit être à l'intérieur de la fenêtre d'au moins 1 pixel, essayez donc cette fonction:

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

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

43
2018-05-03 17:00



Il y a un plugin jQuery appelé en vue ça fait le boulot


25
2017-09-25 12:54



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

En passant, le différence entre window.innerWidth et document.documentElement.clientWidth est que clientWidth / clientHeight n'inclut pas la barre de défilement, alors que window.innerWidth / Height le fait.

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>

25
2017-09-14 05:49



Voir la source de bord, 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 
    );

}

Résultats true si tout une partie de l'élément est dans la fenêtre.


23
2017-08-02 13:40



Je trouvais troublant qu'il n'y ait pas de jQuery version centralisée de la fonctionnalité disponible. Quand je suis tombé sur La solution de Dan j'ai espionné l'occasion de fournir quelque chose pour les gens qui aiment programmer dans le jQuery Style OO. Soyez sûr de faire défiler vers le haut et de laisser un upvote sur le code de Dan. C'est agréable et vif et fonctionne comme un charme pour moi.

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

});

19
2018-04-23 03:04



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

ajouter jsFiddle au besoin https://jsfiddle.net/on1g619L/1/


18
2018-01-30 14:49



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

6
2018-04-24 15:12