Wie kann ich feststellen, ob ein DOM-Element im aktuellen Ansichtsfenster sichtbar ist?

949

Gibt es eine effiziente Methode, um festzustellen, ob ein DOM-Element (in einem HTML-Dokument) derzeit sichtbar ist (im Ansichtsfenster angezeigt wird )?

(Die Frage bezieht sich auf Firefox.)

Benzaita
quelle
Kommt darauf an, was du mit sichtbar meinst. Wenn Sie meinen, dass es derzeit auf der Seite angezeigt wird, können Sie es anhand der Bildlaufposition basierend auf den Elementen y-Versatz und der aktuellen Bildlaufposition berechnen.
Roryf
18
Erläuterung: Sichtbar wie im aktuell angezeigten Rechteck. Sichtbar, wie in nicht versteckt oder Anzeige: keine? Sichtbar, wie nicht hinter etwas anderem in der Z-Reihenfolge?
Adam Wright
6
wie innerhalb des aktuell angezeigten Rechtecks. Vielen Dank. Umformuliert.
Benzaita
2
Beachten Sie, dass alle Antworten hier ein falsches Positiv für Elemente ergeben, die sich außerhalb des sichtbaren Bereichs ihres übergeordneten Elements befinden (z. B. mit ausgeblendetem Überlauf), aber innerhalb des Ansichtsfensterbereichs.
Andy E
1
Es gibt eine Million Antworten und die meisten sind lächerlich lang. Sehen Sie hier für einen
Zweiliner

Antworten:

347

Update: Die Zeit vergeht und unsere Browser auch. Diese Technik wird nicht mehr empfohlen und Sie sollten Dans Lösung verwenden, wenn Sie die Version von Internet Explorer nicht vor 7 unterstützen müssen.

Ursprüngliche Lösung (jetzt veraltet):

Dadurch wird überprüft, ob das Element im aktuellen Ansichtsfenster vollständig sichtbar ist:

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

Sie können dies einfach ändern, um festzustellen, ob ein Teil des Elements im Ansichtsfenster sichtbar ist:

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
quelle
1
Die ursprüngliche Funktion hatte einen Fehler.
Muss
15
Was ist, wenn das Element in einem scrollbaren Div lebt und aus einer Ansicht gescrollt wird?
Amartynov
2
Bitte überprüfen Sie eine neuere Version des Skripts unten
Dan
1
Auch neugierig auf @ amartynovs Frage. Weiß jemand, wie man einfach erkennt, ob ein Element aufgrund eines Überlaufs eines Vorfahrenelements ausgeblendet ist? Bonus, wenn dies unabhängig davon erkannt werden kann, wie tief das Kind verschachtelt ist.
Eric Nguyen
1
@deadManN, das durch das DOM rekursiert, ist notorisch langsam. Grund genug, aber die Browser-Anbieter haben auch getBoundingClientRectspeziell für die Suche nach Elementkoordinaten erstellt ... Warum sollten wir sie nicht verwenden?
Prestaul
1402

Jetzt unterstützen die meisten Browser die Methode getBoundingClientRect , die zur Best Practice geworden ist. Die Verwendung einer alten Antwort ist sehr langsam , nicht genau und weist mehrere Fehler auf .

Die als richtig gewählte Lösung ist fast nie präzise . Sie können mehr über seine Fehler lesen .


Diese Lösung wurde auf Internet Explorer 7 (und höher), iOS 5 (und höher) Safari, Android 2.0 (Eclair) und höher, BlackBerry, Opera Mobile und Internet Explorer Mobile 9 getestet .


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

Wie benutzt man:

Sie können sicher sein, dass die oben angegebene Funktion zum Zeitpunkt des Aufrufs die richtige Antwort zurückgibt. Wie steht es jedoch mit der Sichtbarkeit des Elements als Ereignis?

Platzieren Sie den folgenden Code am unteren Rand Ihres <body>Tags:

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

Wenn Sie DOM-Änderungen vornehmen, können diese natürlich die Sichtbarkeit Ihres Elements ändern.

Richtlinien und häufige Fallstricke:

Möglicherweise müssen Sie den Seitenzoom / die Prise des Mobilgeräts verfolgen? jQuery sollte den Cross-Browser Zoom / Pinch verarbeiten , andernfalls sollte der erste oder zweite Link Ihnen helfen.

Wenn Sie das DOM ändern , kann dies die Sichtbarkeit des Elements beeinträchtigen. Sie sollten die Kontrolle darüber übernehmen und handler()manuell anrufen . Leider haben wir kein browserübergreifendes onrepaintEreignis. Auf der anderen Seite können wir Optimierungen vornehmen und nur DOM-Änderungen überprüfen, die die Sichtbarkeit eines Elements ändern können.

Verwenden Sie es niemals nur in jQuery $ (document) .ready () , da in diesem Moment keine Garantie für CSS besteht . Ihr Code kann lokal mit Ihrem CSS auf einer Festplatte arbeiten, aber sobald er auf einem Remote-Server installiert ist, schlägt er fehl.

Nach dem DOMContentLoadedAuslösen werden Stile angewendet , aber die Bilder werden noch nicht geladen . Wir sollten also den window.onloadEreignis-Listener hinzufügen .

Wir können das Zoom / Pinch-Ereignis noch nicht erfassen.

Der letzte Ausweg könnte der folgende Code sein:

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

Sie können die fantastische Funktion pageVisibiliy der HTML5-API verwenden, wenn Sie sich dafür interessieren, ob die Registerkarte mit Ihrer Webseite aktiv und sichtbar ist.

TODO: Diese Methode behandelt nicht zwei Situationen:

Dan
quelle
6
Ich verwende diese Lösung (Vorsicht vor dem Tippfehler "botom"). Es gibt auch etwas zu beachten, wenn das Element, das wir betrachten, Bilder enthalten würde. Chrome muss (zumindest) warten, bis das Bild geladen ist, damit es den genauen Wert für das BoundingRectangle hat. Scheint, dass Firefox dieses "Problem" nicht hat
Claudio
4
Funktioniert es, wenn Sie das Scrollen in einem Container innerhalb des Körpers aktiviert haben? Zum Beispiel funktioniert es hier nicht - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). Innerele befindet sich in einem Container, in dem das Scrollen aktiviert ist.
Agaase
70
Die Berechnungen gehen davon aus, dass das Element kleiner als der Bildschirm ist. Wenn Sie hohe oder breite Elemente haben, ist es möglicherweise genauer zu verwendenreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan
11
Tipp: Für diejenigen, die versuchen, dies mit jQuery zu implementieren, nur eine freundliche Erinnerung, das HTML-DOM-Objekt (z. B. isElementInViewport(document.getElementById('elem'))) und nicht das jQuery-Objekt (z isElementInViewport($("#elem)). B. ) zu übergeben. Das jQuery-Äquivalent lautet [0]wie folgt : isElementInViewport($("#elem)[0]).
Juli
11
el is not defined
M_Willett
176

Aktualisieren

In modernen Browsern möchten Sie möglicherweise die Intersection Observer-API ausprobieren, die die folgenden Vorteile bietet:

  • Bessere Leistung als das Abhören von Bildlaufereignissen
  • Funktioniert in domänenübergreifenden Iframes
  • Kann erkennen, ob ein Element ein anderes blockiert / schneidet

Intersection Observer ist auf dem Weg zu einem vollwertigen Standard und wird bereits in Chrome 51+, Edge 15+ und Firefox 55+ unterstützt und befindet sich in der Entwicklung für Safari. Es gibt auch eine Polyfüllung .


Vorherige Antwort

Es gibt einige Probleme mit der Antwort von Dan , die es für einige Situationen zu einem ungeeigneten Ansatz machen könnten. Einige dieser Probleme werden in seiner Antwort am unteren Rand darauf hingewiesen, dass sein Code falsch positive Ergebnisse für Elemente liefert, die:

  • Versteckt durch ein anderes Element vor dem zu testenden
  • Außerhalb des sichtbaren Bereichs eines übergeordneten oder Vorfahrenelements
  • Ein Element oder seine untergeordneten Elemente, die mithilfe der CSS- clipEigenschaft ausgeblendet wurden

Diese Einschränkungen werden in den folgenden Ergebnissen eines einfachen Tests gezeigt :

Fehlgeschlagener Test mit isElementInViewport

Die Lösung: isElementVisible()

Hier finden Sie eine Lösung für diese Probleme mit dem folgenden Testergebnis und einer Erläuterung einiger Teile des Codes.

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

Bestandener Test: http://jsfiddle.net/AndyE/cAY8c/

Und das Ergebnis:

Bestandener Test mit isElementVisible

Zusätzliche Bemerkungen

Diese Methode ist jedoch nicht ohne Einschränkungen. Zum Beispiel würde ein Element, das mit einem niedrigeren Z-Index als ein anderes Element an derselben Stelle getestet wird, als versteckt identifiziert, selbst wenn das vordere Element tatsächlich keinen Teil davon verbirgt. Dennoch hat diese Methode in einigen Fällen ihre Verwendung, die Dans Lösung nicht abdeckt.

Beide element.getBoundingClientRect()und document.elementFromPoint()sind Teil der CSSOM Working Draft-Spezifikation und werden in mindestens IE 6 und höher sowie in den meisten Desktop-Browsern lange Zeit unterstützt (wenn auch nicht perfekt). Weitere Informationen finden Sie unter Quirksmode für diese Funktionen .

contains()wird verwendet, um festzustellen, ob das von zurückgegebene Element document.elementFromPoint()ein untergeordneter Knoten des Elements ist, das wir auf Sichtbarkeit testen. Es wird auch true zurückgegeben, wenn das zurückgegebene Element dasselbe Element ist. Dies macht die Prüfung nur robuster. Es wird in allen gängigen Browsern unterstützt, wobei Firefox 9.0 der letzte ist, der es hinzufügt. Überprüfen Sie für ältere Firefox-Unterstützung den Verlauf dieser Antwort.

Wenn Sie mehr Punkte um das Element herum auf Sichtbarkeit testen möchten, dh um sicherzustellen, dass das Element nicht durch mehr als beispielsweise 50% abgedeckt ist, würde es nicht viel dauern, den letzten Teil der Antwort anzupassen. Beachten Sie jedoch, dass es wahrscheinlich sehr langsam ist, wenn Sie jedes Pixel überprüfen, um sicherzustellen, dass es zu 100% sichtbar ist.

Andy E.
quelle
2
Wollten Sie doc.documentElement.clientWidth verwenden? Sollte das stattdessen 'document.documentElement' sein? Anders gesagt, dies ist die einzige Methode, die auch für Anwendungsfälle wie das Ausblenden des Inhalts eines Elements für die Barrierefreiheit mithilfe der CSS-Eigenschaft 'clip' funktioniert
Kevin Lamping
@klamping: guter Fang, danke. Ich hatte es direkt aus meinem Code kopiert, für den ich es docals Alias ​​verwendet hatte document. Ja, ich betrachte dies gerne als eine anständige Lösung für die Randfälle.
Andy E
2
Bei mir funktioniert es nicht. Aber inViewport () in der vorherigen Antwort funktioniert in FF.
Satya Prakash
9
Es kann auch nützlich sein, zu überprüfen, ob die Mitte des Elements sichtbar ist, wenn abgerundete Ecken oder eine Transformation angewendet wurden, da die Begrenzungsecken möglicherweise nicht das erwartete Element zurückgeben:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared
2
Hat bei mir nicht an Eingängen gearbeitet (Chrome Canary 50). Nicht sicher warum, vielleicht einheimische rundere Ecken? Ich musste die Koordinaten leicht reduzieren, damit es el.contains funktioniert (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

Ich habe Dans Antwort versucht . Die zur Bestimmung der Grenzen verwendete Algebra bedeutet jedoch, dass das Element sowohl ≤ der Größe des Ansichtsfensters als auch vollständig innerhalb des Ansichtsfensters sein muss, um zu erhalten true, was leicht zu falschen Negativen führt. Wenn Sie feststellen möchten, ob sich überhaupt ein Element im Ansichtsfenster befindet, lautet die Antwort von ryanve knapp, das getestete Element sollte sich jedoch mit dem Ansichtsfenster überschneiden. Versuchen Sie Folgendes :

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
quelle
33

Als öffentlicher Dienst:
Dans Antwort mit den richtigen Berechnungen (Element kann> Fenster sein, insbesondere auf Handybildschirmen) und korrekten jQuery-Tests sowie Hinzufügen von isElementPartiallyInViewport:

By the way, der Unterschied ist zwischen window.innerWidth und document.documentElement.clientWidth dass client / client nicht der Bildlaufleiste enthält, während window.innerWidth / Höhe tut.

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

Testfall

<!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
quelle
2
isElementPartiallyInViewportist auch sehr nützlich. Schön.
RJ Cuthbertson
Für isElementInViewport (e): Ihr Code lädt nicht einmal Bilder. Dieser ist korrekt: Funktion 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: Keiner meiner Codes lädt Bilder. Warum sollte das so sein? Die Formel ist korrekt.
Stefan Steiger
1
@targumon: Der Grund ist die Unterstützung alter Browser.
Stefan Steiger
1
@StefanSteiger laut MDN wird es seit IE9 unterstützt, so dass es praktisch sicher ist (zumindest in meinem Fall), nur window.innerHeight direkt zu verwenden. Vielen Dank!
Targumon
28

Siehe die Quelle kurz davor , die verwendet getBoundingClientRect . Es ist wie:

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

}

Es wird zurückgegeben, truewenn sich ein Teil des Elements im Ansichtsfenster befindet.

Ryanve
quelle
27

Meine kürzere und schnellere Version:

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

Und eine jsFiddle nach Bedarf: https://jsfiddle.net/on1g619L/1/

Eric Chen
quelle
4
Meine Lösung ist gieriger und schneller. Wenn ein Element ein Pixel im Ansichtsfenster hat, wird false zurückgegeben.
Eric Chen
1
Ich mag das. Prägnant. Sie können die Leerzeichen zwischen Funktionsname und Klammer sowie zwischen Klammer und Klammer in der ersten Zeile entfernen. Ich mochte diese Räume nie. Vielleicht ist es nur mein Texteditor, der alles farbcodiert, was das Lesen noch erleichtert. Funktion aaa (arg) {Anweisungen} Ich weiß, dass es nicht schneller ausgeführt wird, sondern unter Minimierung fällt.
YK
21

Ich fand es beunruhigend, dass es keine jQuery- zentrierte Version der verfügbaren Funktionalität gab. Als ich auf Dans Lösung stieß, erspähte ich die Gelegenheit, etwas für Leute bereitzustellen, die gerne im jQuery OO-Stil programmieren. Es ist schön und bissig und wirkt wie ein Zauber für mich.

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

Verwendungszweck

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

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});
r3wt
quelle
1
Würde die geschweifte Klammer ausreichen, um die if-Anweisung zu schließen?
Calasyr
1
Ich konnte es nicht mit mehreren Elementen einer identischen Klasse zum Laufen bringen.
The Whiz of Oz
1
@TheWhizofOz Ich habe meine Antwort aktualisiert, um Beispiele für die anderen möglichen Anwendungsfälle zu geben, die Sie angesprochen haben. viel Glück.
r3wt
10

Die neue Intersection Observer API geht diese Frage sehr direkt an.

Diese Lösung benötigt eine Polyfüllung, da Safari, Opera und Internet Explorer dies noch nicht unterstützen (die Polyfüllung ist in der Lösung enthalten).

In dieser Lösung befindet sich eine Box außerhalb der Sicht, die das Ziel ist (beobachtet). Wenn es angezeigt wird, ist die Schaltfläche oben in der Kopfzeile ausgeblendet. Es wird angezeigt, sobald das Feld die Ansicht verlässt.

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
quelle
3
Gute Umsetzung, und nach dem Link in dieser Antwort es sollte auf Safari arbeitet durch Zugabe <!DOCTYPE html>zu dem HTML
Alon Eitan
Beachten Sie, dass dies IntersectionObservereine experimentelle Funktion ist (die sich in Zukunft ändern kann).
Karthik Chintala
2
@KarthikChintala - es wird in jedem Browser außer IE unterstützt - und es ist auch eine Polyfüllung verfügbar.
Randy Casburn
Behandelt die Frage des OP nicht, da nur Änderungen erkannt werden : Der IntersectionObserverRückruf wird erst ausgelöst, nachdem das Ziel relativ zur Wurzel bewegt wurde.
Brandon Hill
Wenn ein aufrufendes observeEreignis ausgelöst wird, werden Sie sofort über den aktuellen Schnittpunktstatus des verfolgten Elements informiert. Also irgendwie - es spricht an.
Mesqalito
8

Alle Antworten, auf die ich hier gestoßen bin, prüfen nur, ob sich das Element im aktuellen Ansichtsfenster befindet . Das heißt aber nicht, dass es sichtbar ist .
Was ist, wenn sich das angegebene Element in einem Div mit überfülltem Inhalt befindet und nicht mehr sichtbar ist?

Um dies zu lösen, müssten Sie überprüfen, ob das Element von allen Eltern enthalten ist.
Meine Lösung macht genau das:

Außerdem können Sie festlegen, wie viel des Elements sichtbar sein muss.

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

Diese Lösung ignorierte die Tatsache, dass Elemente aufgrund anderer Fakten wie z opacity: 0.

Ich habe diese Lösung in Chrome und Internet Explorer 11 getestet.

Domysee
quelle
8

Ich finde, dass die hier akzeptierte Antwort für die meisten Anwendungsfälle zu kompliziert ist. Dieser Code macht den Job gut (mit jQuery) und unterscheidet zwischen vollständig sichtbaren und teilweise sichtbaren Elementen:

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
quelle
Sie sollten auf jeden Fall $window = $(window)außerhalb des Scroll-Handlers zwischenspeichern.
Sam
4

Die einfachste Lösung als Unterstützung von Element.getBoundingClientRect () ist perfekt geworden :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}
leonheess
quelle
Wie verhält sich das in mobilen Browsern? Die meisten von ihnen sind in Bezug auf das Ansichtsfenster fehlerhaft. Der Header wird beim Scrollen nach oben oder unten verschoben, und das Verhalten beim Anzeigen der Tastatur ist unterschiedlich, je nachdem, ob es sich um Android oder iOS usw. handelt.
Kev
@Kev Sollte gut funktionieren, je nachdem, wann Sie diese Methode aufrufen. Wenn Sie es aufrufen und dann die Fenstergröße ändern, ist das Ergebnis möglicherweise nicht mehr korrekt. Sie können es bei jedem Größenänderungsereignis aufrufen, abhängig von der Art der gewünschten Funktionalität. Sie können gerne eine separate Frage zu Ihrem speziellen Anwendungsfall stellen und mich hier anpingen.
Leonheess
3

Ich denke, dies ist ein funktionalerer Weg, dies zu tun. Dans Antwort funktioniert nicht in einem rekursiven Kontext.

Diese Funktion löst das Problem, wenn sich Ihr Element in anderen scrollbaren Divs befindet, indem alle Ebenen rekursiv bis zum HTML-Tag getestet werden und beim ersten false angehalten wird.

/**
 * 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
quelle
3

Die am meisten akzeptierten Antworten funktionieren beim Zoomen in Google Chrome unter Android nicht. In Kombination mit Dans Antwort muss visualViewport verwendet werden, um Chrome unter Android zu berücksichtigen . Das folgende Beispiel berücksichtigt nur die vertikale Prüfung und verwendet jQuery für die Fensterhöhe:

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
quelle
2

Hier ist meine Lösung. Es funktioniert, wenn ein Element in einem scrollbaren Container versteckt ist.

Hier ist eine Demo (versuchen Sie, die Größe des Fensters zu ändern)

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

Ich musste nur überprüfen, ob es auf der Y-Achse sichtbar ist (für eine scrollende Ajax-Funktion zum Laden weiterer Datensätze).

Verbündete
quelle
2

Basierend auf Dans Lösung habe ich versucht, die Implementierung zu bereinigen, damit es einfacher ist, sie mehrmals auf derselben Seite zu verwenden:

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

});

Ich verwende es so, dass ich beim Scrollen des Elements in die Ansicht eine Klasse hinzufüge, die eine CSS-Keyframe-Animation auslöst. Es ist ziemlich einfach und funktioniert besonders gut, wenn Sie mehr als 10 Dinge auf einer Seite bedingt animieren müssen.

Pirijan
quelle
Sie sollten auf jeden Fall $window = $(window)außerhalb des Scroll-
Handlers zwischenspeichern
2

Die meisten Verwendungen in früheren Antworten schlagen an folgenden Punkten fehl:

-Wenn ein Pixel eines Elements sichtbar ist, aber keine " Ecke ",

-Wenn ein Element größer als das Ansichtsfenster und zentriert ist ,

- Die meisten von ihnen suchen nur nach einem einzelnen Element in einem Dokument oder Fenster .

Nun, für all diese Probleme habe ich eine Lösung und die positiven Seiten sind:

-Sie können zurückkehren, visiblewenn nur ein Pixel von einer Seite angezeigt wird und keine Ecke ist.

-Sie können immer noch zurückkehren, visiblewenn das Element größer als das Ansichtsfenster ist.

-Sie können Ihre auswählen parent elementoder Sie können sie automatisch auswählen lassen,

- Arbeitet auch an dynamisch hinzugefügten Elementen .

Wenn Sie die folgenden Ausschnitte überprüfen, werden Sie feststellen, dass der Unterschied bei der Verwendung overflow-scrollim Container des Elements keine Probleme verursacht und dass im Gegensatz zu anderen Antworten hier ein Pixel von einer Seite angezeigt wird oder wenn ein Element größer als das Ansichtsfenster ist und wir das Innere sehen Pixel des Elements funktioniert es noch.

Die Verwendung ist einfach:

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

Eine Demonstration ohne crossSearchAlgorithmdiese ist nützlich für Elemente, die größer als das Ansichtsfenster sind. Überprüfen Sie die inneren Pixel von Element3, um Folgendes zu sehen:

Sie sehen, wenn Sie sich innerhalb des Elements3 befinden, kann es nicht erkennen, ob es sichtbar ist oder nicht, da wir nur prüfen, ob das Element von Seiten oder Ecken sichtbar ist .

Und dies beinhaltet crossSearchAlgorithm, dass Sie immer noch zurückkehren können, visiblewenn das Element größer als das Ansichtsfenster ist:

JSFiddle zum Spielen: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Dieser Code dient zur genaueren Information, ob ein Teil des Elements in der Ansicht angezeigt wird oder nicht. Verwenden Sie diese Option nicht für Leistungsoptionen oder nur für vertikale Folien! Dieser Code ist beim Zeichnen von Fällen effektiver.

Berker Yüceer
quelle
1

Eine bessere Lösung:

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
quelle
12
Sie sollten versuchen zu erklären, warum Ihre Version besser ist. So wie es aussieht, sieht es mehr oder weniger genauso aus wie die anderen Lösungen.
Andy E
1
Tolle Lösung, es hat einen BOX / ScrollContainer und verwendet das WINDOW nicht (nur wenn es nicht angegeben ist).
Werfen
1

So einfach wie es nur geht, IMO:

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

Hier ist eine Funktion, die angibt, ob ein Element im aktuellen Ansichtsfenster eines übergeordneten Elements sichtbar ist:

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
quelle
0

Dies prüft, ob ein Element zumindest teilweise sichtbar ist (vertikale Abmessung):

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
quelle
0

Ich hatte die gleiche Frage und fand sie mit getBoundingClientRect () heraus.

Dieser Code ist vollständig "generisch" und muss nur einmal geschrieben werden, damit er funktioniert (Sie müssen ihn nicht für jedes Element ausschreiben, von dem Sie wissen möchten, dass es sich im Ansichtsfenster befindet).

Dieser Code prüft nur, ob er sich vertikal im Ansichtsfenster befindet, nicht horizontal . In diesem Fall enthält die Variable (Array) 'Elemente' alle Elemente, die Sie als vertikal im Ansichtsfenster überprüfen möchten. Nehmen Sie also alle gewünschten Elemente an einer beliebigen Stelle und speichern Sie sie dort.

Die 'for-Schleife' durchläuft jedes Element und prüft, ob es sich vertikal im Ansichtsfenster befindet. Dieser Code wird jedes Mal ausgeführt, wenn der Benutzer einen Bildlauf durchführt! Wenn getBoudingClientRect (). Top weniger als 3/4 des Ansichtsfensters beträgt (das Element ist ein Viertel im Ansichtsfenster), wird es als "im Ansichtsfenster" registriert.

Da der Code generisch ist, möchten Sie wissen, welches Element sich im Ansichtsfenster befindet. Um dies herauszufinden, können Sie es anhand eines benutzerdefinierten Attributs, eines Knotennamens, einer ID, eines Klassennamens usw. ermitteln.

Hier ist mein Code (sagen Sie mir, wenn er nicht funktioniert; er wurde in Internet Explorer 11, Firefox 40.0.3, Chrome Version 45.0.2454.85 m, Opera 31.0.1889.174 und Edge mit Windows 10 getestet [noch nicht 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
quelle
0

Dies ist die einfache und kleine Lösung, die für mich funktioniert hat.

Beispiel : Sie möchten sehen, ob das Element im übergeordneten Element mit Überlauf-Bildlauf sichtbar ist.

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

Alle Antworten hier bestimmen, ob das Element vollständig im Ansichtsfenster enthalten ist und nicht nur auf irgendeine Weise sichtbar ist. Wenn beispielsweise nur die Hälfte eines Bildes am unteren Rand der Ansicht sichtbar ist, schlagen die Lösungen hier fehl, wenn man bedenkt, dass "außen".

Ich hatte einen Anwendungsfall, in dem ich verzögert über lade IntersectionObserver, aber aufgrund von Animationen, die während des Pop-Ins auftreten, wollte ich keine Bilder beobachten, die bereits beim Laden der Seite geschnitten wurden. Dazu habe ich folgenden Code verwendet:

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

Hierbei wird im Wesentlichen überprüft, ob sich die obere oder untere Grenze unabhängig voneinander im Ansichtsfenster befindet. Das gegenüberliegende Ende kann außerhalb sein, aber solange ein Ende innen ist, ist es zumindest teilweise "sichtbar".

Chris Pratt
quelle
-1

Ich benutze diese Funktion (sie prüft nur, ob das y im Bildschirm ist, da das x die meiste Zeit nicht benötigt wird)

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
quelle
-3

Für eine ähnliche Herausforderung hat mir dieser Kern sehr gut gefallen, der eine Polyfüllung für scrollIntoViewIfNeeded () verfügbar macht .

In diesem Block befindet sich alles notwendige Kung Fu, um zu antworten:

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;

thisbezieht sich auf das Element, das Sie wissen möchten, wenn es zum Beispiel ist, overTopoder overBottom- Sie sollten nur die Drift bekommen ...

Philzen
quelle