Jak sprawdzić, czy element DOM jest widoczny w bieżącej rzutni?


949

Czy istnieje skuteczny sposób stwierdzenia, czy element DOM (w dokumencie HTML) jest obecnie widoczny (pojawia się w rzutni )?

(Pytanie dotyczy przeglądarki Firefox).


Zależy, co rozumiesz przez widoczny. Jeśli masz na myśli, czy jest on aktualnie wyświetlany na stronie, biorąc pod uwagę pozycję przewijania, możesz obliczyć ją na podstawie przesunięcia elementów y i bieżącej pozycji przewijania.
roryf

18
Wyjaśnienie: Widoczne, jak w obrębie aktualnie wyświetlanego prostokąta. Widoczny, jak nie ukryty ani wyświetlany: brak? Widoczny, ponieważ nie kryje się za nim coś innego w kolejności Z?
Adam Wright,

6
jak w obecnie wyświetlanym prostokącie. Dzięki. Przeformułowano.
benzaita

2
Zauważ, że wszystkie odpowiedzi tutaj dają fałszywy wynik pozytywny dla elementów, które znajdują się poza widocznym obszarem ich elementu nadrzędnego (eh z ukrytym przepełnieniem), ale wewnątrz obszaru rzutni.
Andy E

1
Istnieje milion odpowiedzi, a większość z nich jest absurdalnie długa. Zobacz tutaj
dwuliniową

Odpowiedzi:


346

Aktualizacja: czas mija, podobnie jak nasze przeglądarki. Ta technika nie jest już zalecana i powinieneś skorzystać z rozwiązania Dana, jeśli nie potrzebujesz obsługiwać wersji Internet Explorera przed 7.

Oryginalne rozwiązanie (teraz nieaktualne):

Spowoduje to sprawdzenie, czy element jest całkowicie widoczny w bieżącej rzutni:

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

Możesz to zmodyfikować, aby określić, czy jakaś część elementu jest widoczna w rzutni:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

1
Oryginalna opublikowana funkcja miała błąd. Musiałem zapisać szerokość / wysokość przed ponownym przypisaniem el ...
Prestaul

15
Co jeśli element żyje w przewijalnym div i przewinięty z widoku?
amartynov,

2
Zapoznaj się z nowszą wersją skryptu poniżej
Dan

1
Ciekawe też pytanie @ amartynov. Czy ktoś wie, jak po prostu stwierdzić, czy element jest ukryty z powodu przepełnienia elementu przodka? Bonus, jeśli można to wykryć niezależnie od tego, jak głęboko dziecko jest zagnieżdżone.
Eric Nguyen,

1
@deadManN rekurencja w DOM jest notorycznie powolna. To wystarczający powód, ale dostawcy przeglądarki stworzyli również getBoundingClientRectw celu znalezienia współrzędnych elementów ... Dlaczego nie mielibyśmy go użyć?
Prestaul

1402

Teraz większość przeglądarek obsługuje metodę getBoundingClientRect , która stała się najlepszą praktyką. Użycie starej odpowiedzi jest bardzo wolne , niedokładne i zawiera kilka błędów .

Rozwiązanie wybrane jako prawidłowe prawie nigdy nie jest precyzyjne . Możesz przeczytać więcej o jego błędach.


To rozwiązanie zostało przetestowane na Internet Explorerze 7 (i późniejszych), iOS 5 (i późniejszych) Safari, Android 2.0 (Eclair) i późniejszych, BlackBerry, Opera Mobile i 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() */
    );
}

Jak używać:

Możesz być pewien, że podana powyżej funkcja zwraca poprawną odpowiedź w momencie jej wywołania, ale co z widocznością elementu śledzącego jako zdarzenia?

Umieść następujący kod na dole <body>tagu:

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

Jeśli dokonasz jakichkolwiek modyfikacji DOM, mogą one oczywiście zmienić widoczność Twojego elementu.

Wytyczne i typowe pułapki:

Może potrzebujesz śledzić powiększenie strony / uszczypnięcie urządzenia mobilnego? jQuery powinien obsługiwać przeglądarkę Zoom / Pinch w różnych przeglądarkach, w przeciwnym razie pierwszy lub drugi link powinien ci pomóc.

Jeśli zmodyfikujesz DOM , może to wpłynąć na widoczność elementu. Powinieneś przejąć nad tym kontrolę i zadzwonić handler()ręcznie. Niestety nie mamy żadnych onrepaintwydarzeń w różnych przeglądarkach . Z drugiej strony pozwala nam to na optymalizacje i ponowne sprawdzenie tylko tych modyfikacji DOM, które mogą zmienić widoczność elementu.

Nigdy nie używaj go tylko w jQuery $ (dokument) .ready () , ponieważ w tej chwili nie ma gwarancji CSS . Twój kod może współpracować lokalnie z CSS na dysku twardym, ale po umieszczeniu na zdalnym serwerze zawiedzie.

Po DOMContentLoadedwystrzeleniu, style są stosowane , ale zdjęcia nie są jeszcze załadowany . Powinniśmy dodać window.onloaddetektor zdarzeń.

Nie możemy jeszcze złapać zdarzenia powiększenia / uszczypnięcia.

Ostatnim rozwiązaniem może być następujący kod:

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

Możesz skorzystać z niesamowitej strony funkcji Visiiliili interfejsu API HTML5, jeśli zależy Ci na tym, czy karta z twoją stroną jest aktywna i widoczna.

DO ZROBIENIA: ta metoda nie obsługuje dwóch sytuacji:


6
Korzystam z tego rozwiązania (uważaj jednak na literówkę „botom”). Jest też coś, o czym należy pamiętać, gdy rozważany element zawiera w sobie obrazy. Chrome (przynajmniej) musi poczekać, aż obraz zostanie załadowany, aby mieć dokładną wartość granicznego prostokąta. Wygląda na to, że Firefox nie ma tego „problemu”
Claudio,

4
Czy to działa, gdy masz włączone przewijanie w kontenerze wewnątrz ciała. Na przykład nie działa tutaj - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById („innerele”)). innerele jest obecny w kontenerze z włączonym przewijaniem.
agaase,

70
Obliczenia zakładają, że element jest mniejszy niż ekran. Jeśli masz wysokie lub szerokie elementy, może być bardziej dokładny w użyciureturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan

11
Wskazówka: dla tych, którzy próbują to zaimplementować za pomocą jQuery, wystarczy przyjazne przypomnienie, aby przekazać obiekt HTML DOM (np. isElementInViewport(document.getElementById('elem'))), A nie obiekt jQuery (np isElementInViewport($("#elem)).). Równowartość jQuery jest dodanie [0]tak: isElementInViewport($("#elem)[0]).
jiminy

11
el is not defined
M_Willett,

175

Aktualizacja

W nowoczesnych przeglądarkach warto sprawdzić interfejs API Intersection Observer, który zapewnia następujące korzyści:

  • Lepsza wydajność niż słuchanie zdarzeń przewijania
  • Działa w ramach iframe między domenami
  • Potrafi stwierdzić, czy element przeszkadza / przecina inny

Intersection Observer jest na dobrej drodze, aby stać się pełnoprawnym standardem i jest już obsługiwany w Chrome 51+, Edge 15+ i Firefox 55+ i jest w fazie rozwoju dla Safari. Dostępna jest również polifill .


Poprzednia odpowiedź

Istnieją pewne problemy z odpowiedzią udzieloną przez Dana, które mogą sprawić, że będzie to nieodpowiednie podejście w niektórych sytuacjach. Niektóre z tych problemów są wskazane w jego odpowiedzi u dołu, że jego kod da fałszywe trafienia dla elementów, które są:

  • Ukryty przez inny element przed testowanym
  • Poza widocznym obszarem elementu nadrzędnego lub przodka
  • Element lub jego dzieci ukryte za pomocą clipwłaściwości CSS

Ograniczenia te zostały przedstawione w następujących wynikach prostego testu :

Test zakończony niepowodzeniem przy użyciu isElementInViewport

Rozwiązanie: isElementVisible()

Oto rozwiązanie tych problemów, z poniższym wynikiem testu i objaśnieniem niektórych części kodu.

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

Zaliczenie testu: http://jsfiddle.net/AndyE/cAY8c/

A wynik:

Test zdany przy użyciu isElementVisible

Dodatkowe uwagi

Ta metoda nie jest jednak pozbawiona własnych ograniczeń. Na przykład element testowany z niższym indeksem Z niż inny element w tym samym miejscu zostałby zidentyfikowany jako ukryty, nawet jeśli element z przodu faktycznie nie ukrywa żadnej jego części. Mimo to metoda ta ma zastosowanie w niektórych przypadkach, których rozwiązanie Dana nie obejmuje.

Zarówno element.getBoundingClientRect()i document.elementFromPoint()są częścią projektu specyfikacji CSSOM roboczej i są obsługiwane w co najmniej IE 6 i nowszych, a większość przeglądarek pulpitu przez długi czas (choć nie idealnie). Zobacz Quirksmode na temat tych funkcji, aby uzyskać więcej informacji.

contains()służy do sprawdzenia, czy zwracany element document.elementFromPoint()jest węzłem potomnym elementu, który testujemy pod kątem widoczności. Zwraca również wartość true, jeśli zwracany element jest tym samym elementem. Dzięki temu czek jest bardziej niezawodny. Jest obsługiwany we wszystkich głównych przeglądarkach, a Firefox 9.0 jest ostatnim z nich, aby go dodać. Aby uzyskać starszą obsługę Firefoksa, sprawdź historię tej odpowiedzi.

Jeśli chcesz przetestować więcej punktów wokół elementu pod kątem widoczności ―, tj. Aby upewnić się, że element nie jest objęty więcej niż, powiedzmy, 50% ―, nie zajęłoby wiele, aby dopasować ostatnią część odpowiedzi. Pamiętaj jednak, że prawdopodobnie sprawdziłby się każdy piksel, aby upewnić się, że jest w 100% widoczny.


2
Czy miałeś na myśli użyć doc.documentElement.clientWidth? Czy zamiast tego powinien to być „document.documentElement”? Z drugiej strony, jest to jedyna metoda, która działa również w przypadkach użycia, takich jak ukrywanie zawartości elementu dla ułatwienia dostępu za pomocą właściwości CSS „clip”: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping

@klamping: dobry połów, dzięki. Skopiowałem go bezpośrednio z mojego kodu, którego używałem docjako aliasu document. Tak, lubię myśleć o tym jako o przyzwoitym rozwiązaniu w przypadku skrzynek.
Andy E

2
Dla mnie to nie działa. Ale inViewport () w poprzedniej odpowiedzi działa w FF.
Satya Prakash,

9
Korzystne może być również sprawdzenie, czy środek elementu jest widoczny, jeśli zastosowano zaokrąglone narożniki lub zastosowano transformację, ponieważ narożniki ograniczające mogą nie zwrócić oczekiwanego elementu:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared,

2
Dla mnie nie działał na wejściach (chrom kanarek 50). Nie wiesz, dlaczego, może rodzime zaokrąglone rogi? Musiałem nieznacznie zmniejszyć kable, aby działał el. Zawiera (efp (rect.left + 1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.top + 1)) || el. zawiera (efp (rect.right-1, rect.bottom-1)) || el.contains (efp (rect.left + 1, rect.bottom-1))
Rayjax

65

Próbowałem odpowiedzi Dana . Jednak algebra zastosowana do określenia granic oznacza, że ​​element musi być zarówno ≤ wielkości rzutni, jak i całkowicie wewnątrz rzutni, aby uzyskać true, co łatwo prowadzi do fałszywych negatywów. Jeśli chcesz ustalić, czy element jest w ogóle w rzutni, odpowiedź Ryanve jest bliska, ale testowany element powinien nakładać się na rzutnię, więc spróbuj tego:

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

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

33

Jako usługa publiczna:
odpowiedź Dana z poprawnymi obliczeniami (elementem może być> okno, szczególnie na ekranach telefonów komórkowych) i poprawne testowanie jQuery, a także dodanie isElementPartIALInViewport:

Nawiasem mówiąc, różnica między window.innerWidth i document.documentElement.clientWidth polega na tym, że clientWidth / clientHeight nie zawiera paska przewijania, podczas gdy 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);
}

Przypadek testowy

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Test</title>
    <!--
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="scrollMonitor.js"></script>
    -->

    <script type="text/javascript">

        function isElementPartiallyInViewport(el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
            var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
            var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

            return (vertInView && horInView);
        }


        // http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
        function isElementInViewport (el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            return (
                   (rect.left >= 0)
                && (rect.top >= 0)
                && ((rect.left + rect.width) <= windowWidth)
                && ((rect.top + rect.height) <= windowHeight)
            );
        }


        function fnIsVis(ele)
        {
            var inVpFull = isElementInViewport(ele);
            var inVpPartial = isElementPartiallyInViewport(ele);
            console.clear();
            console.log("Fully in viewport: " + inVpFull);
            console.log("Partially in viewport: " + inVpPartial);
        }


        // var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
        // var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
    </script>
</head>

<body>
    <div style="display: block; width: 2000px; height: 10000px; background-color: green;">

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
        <div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
        t
        </div>

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
    </div>

    <!--
    <script type="text/javascript">

        var element = document.getElementById("myele");
        var watcher = scrollMonitor.create(element);

        watcher.lock();

        watcher.stateChange(function() {
            console.log("state changed");
            // $(element).toggleClass('fixed', this.isAboveViewport)
        });
    </script>
    -->
</body>
</html>

2
isElementPartiallyInViewportjest również bardzo przydatny. Niezłe.
RJ Cuthbertson

Dla isElementInViewport (e): Twój kod nie ładuje nawet obrazów, Ten jest poprawny: funkcja 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: Żaden z moich kodów nie ładuje obrazów, więc dlaczego powinienem, a formuła jest poprawna.
Stefan Steiger,

1
@targumon: Powodem jest obsługa starych przeglądarek.
Stefan Steiger

1
@StefanSteiger według MDN jest obsługiwany od IE9, więc praktycznie (przynajmniej w moim przypadku) jest bezpieczne bezpośrednie użycie window.innerHeight. Dzięki!
targumon

28

Zobacz źródło verge , które korzysta z getBoundingClientRect . To jest jak:

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

}

Zwraca, truejeśli jakaś część elementu znajduje się w rzutni.


27

Moja krótsza i szybsza wersja:

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

I jsFiddle zgodnie z wymaganiami: https://jsfiddle.net/on1g619L/1/


4
Moje rozwiązanie jest bardziej chciwe i szybsze, gdy element ma piksel w polu wyświetlania, zwróci wartość false.
Eric Chen

1
Lubię to. Zwięzły. Możesz usunąć spacje między nazwą funkcji a nawiasami oraz między nawiasami i nawiasami, w pierwszym wierszu. Nigdy nie lubiłem tych przestrzeni. Może to tylko mój edytor tekstu koduje to wszystko, co wciąż ułatwia czytanie. function aaa (arg) {instrukcje} Wiem, że to nie przyspiesza jego działania, zamiast tego podlega niedozwoleniu.
YK

21

Niepokoiło mnie to, że nie było dostępnej dla jQuery wersji funkcjonalności. Kiedy natknąłem się na rozwiązanie Dana, zauważyłem możliwość zapewnienia czegoś dla ludzi, którzy lubią programować w stylu jQuery OO. Jest miły i zgryźliwy i działa jak urok dla mnie.

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

Stosowanie

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

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

1
Czy nawias klamrowy byłby wystarczający do zamknięcia instrukcji if?
calasyr

1
Nie mogłem sprawić, aby działał z wieloma elementami tej samej klasy.
The Whiz of Oz

1
@TheWhizofOz Zaktualizowałem moją odpowiedź, aby podać przykłady innych możliwych przypadków użycia, które przedstawiłeś. powodzenia.
r3wt

10

Nowy interfejs API Intersection Observer rozwiązuje to pytanie bardzo bezpośrednio.

To rozwiązanie będzie wymagało wypełniania polifill, ponieważ Safari, Opera i Internet Explorer nie obsługują tego jeszcze (wypełnienie jest zawarte w rozwiązaniu).

W tym rozwiązaniu pole jest poza polem widzenia, które jest celem (obserwowanym). Jeśli chodzi o widok, przycisk u góry nagłówka jest ukryty. Jest pokazywany, gdy pudełko opuści widok.

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

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

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

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

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


3
Dobra realizacja, a według link w tej odpowiedzi nie powinien pracować na safari przez dodanie <!DOCTYPE html>do HTML
Alon Eitan

Pamiętaj, że IntersectionObserverjest to funkcja eksperymentalna (która może ulec zmianie w przyszłości).
Karthik Chintala

2
@KarthikChintala - jest obsługiwany w każdej przeglądarce z wyjątkiem IE - i jest również dostępna polifill.
Randy Casburn

Nie rozwiązuje pytania PO, ponieważ wykrywa tylko zmiany : IntersectionObserverwyzwala tylko oddzwonienie po ruchu celu względem korzenia.
Brandon Hill,

Po wywołaniu observezdarzenia natychmiast zostaje wyzwolony komunikat informujący o bieżącym stanie przecięcia śledzonego elementu. Tak więc, w jakiś sposób - dotyczy.
Mesqalito

8

Wszystkie odpowiedzi, które tu napotkałem, sprawdzają tylko, czy element jest umieszczony w bieżącej rzutni . Ale to nie znaczy, że jest widoczne .
Co jeśli dany element znajduje się w div z przepełnioną zawartością i jest przewijany poza pole widzenia?

Aby rozwiązać ten problem, musisz sprawdzić, czy element jest zawarty przez wszystkich rodziców.
Moje rozwiązanie robi dokładnie to:

Pozwala także określić, jak duży element ma być widoczny.

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

To rozwiązanie zignorowało fakt, że elementy mogą nie być widoczne z powodu innych faktów, takich jak opacity: 0.

Przetestowałem to rozwiązanie w Chrome i Internet Explorer 11.


8

Uważam, że przyjęta tutaj odpowiedź jest zbyt skomplikowana w przypadku większości przypadków użycia. Ten kod dobrze wykonuje zadanie (używając jQuery) i rozróżnia elementy w pełni widoczne i częściowo widoczne:

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

Zdecydowanie powinieneś buforować $window = $(window)poza modułem przewijania.
sam

4

Najprostsze rozwiązanie jako obsługa elementu Element.getBoundingClientRect () stało się idealne :

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

Jak to działa w przeglądarkach mobilnych? Większość z nich ma problem z wyświetlaniem, nagłówek przewija się w górę lub w dół podczas przewijania, a inne zachowanie pojawia się, gdy pojawia się klawiatura, w zależności od tego, czy jest to Android, iOS itp.
Kev

@ Kev Powinno działać dobrze, w zależności od tego, kiedy wywołasz tę metodę. Jeśli go wywołasz, a następnie zmienisz rozmiar okna, wynik może oczywiście nie być poprawny. Możesz to nazwać przy każdym zdarzeniu zmiany rozmiaru w zależności od rodzaju pożądanej funkcjonalności. Zadaj osobne pytanie dotyczące konkretnego przypadku użycia i ping mnie tutaj.
leonheess

3

Myślę, że jest to bardziej funkcjonalny sposób na zrobienie tego. Odpowiedź Dana nie działa w kontekście rekurencyjnym.

Ta funkcja rozwiązuje problem, gdy element znajduje się wewnątrz innych przewijalnych div div, testując rekursywnie wszystkie poziomy aż do znacznika HTML, i zatrzymuje się przy pierwszym false.

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

3

Najczęściej akceptowane odpowiedzi nie działają podczas powiększania w Google Chrome na Androida. W połączeniu z odpowiedzią Dana , aby uwzględnić Chrome na Androida, należy użyć visualViewport . Poniższy przykład uwzględnia tylko sprawdzenie pionowe i używa jQuery dla wysokości okna:

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

2

Oto moje rozwiązanie. Działa, jeśli element jest ukryty w przewijalnym pojemniku.

Oto demo (spróbuj zmienić rozmiar okna)

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

Musiałem tylko sprawdzić, czy jest widoczny na osi Y (dla przewijanej funkcji ładowania więcej rekordów Ajax).


2

Opierając się na rozwiązaniu dana , postanowiłem wyczyścić implementację, aby korzystanie z niej wielokrotnie na tej samej stronie było łatwiejsze:

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

});

Używam go w ten sposób, że gdy element przewija się do widoku, dodaję klasę, która wyzwala animację klatki kluczowej CSS. Jest to dość proste i działa szczególnie dobrze, gdy masz ponad 10 rzeczy warunkowo animowanych na stronie.


Zdecydowanie powinieneś buforować $window = $(window)poza
modułem

2

Większość zwyczajów z poprzednich odpowiedzi kończy się niepowodzeniem w następujących punktach:

- Gdy widoczny jest dowolny piksel elementu, ale nie „ narożnik ”,

-Gdy element jest większy niż rzutnia i wyśrodkowany ,

-Większość z nich sprawdza tylko pojedynczy element w dokumencie lub oknie .

Cóż, dla wszystkich tych problemów mam rozwiązanie, a plusy to:

-Możesz powrócić, visiblegdy pojawi się tylko piksel z dowolnej strony i nie jest to narożnik,

- Nadal możesz powrócić, visiblegdy element jest większy niż rzutnia,

-Możesz wybrać swój parent element lub możesz automatycznie pozwolić mu wybrać,

- Działa również na dynamicznie dodawanych elementach .

Jeśli sprawdzisz poniższe fragmenty, zobaczysz różnicę w użyciu overflow-scrollw kontenerze elementu nie spowoduje żadnych problemów i zobaczysz, że w przeciwieństwie do innych odpowiedzi tutaj, nawet jeśli piksel pojawia się z dowolnej strony lub gdy element jest większy niż obszar wyświetlania i widzimy wnętrze piksele elementu, który nadal działa.

Użycie jest proste:

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

Demonstracja, bez crossSearchAlgorithmktórej jest przydatna dla elementów większych niż element kontrolny rzutni 3 wewnętrzne piksele, aby zobaczyć:

Widzisz, gdy jesteś w elemencie3 , nie określa , czy jest widoczny, czy nie, ponieważ sprawdzamy tylko, czy element jest widoczny z boku lub z narożników .

I ta zawiera, crossSearchAlgorithmktóra pozwala ci wrócić, visiblegdy element jest większy niż rzutnia:

JSFiddle do zabawy z: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Ten kod ma na celu uzyskanie dokładniejszych informacji, jeśli jakaś część elementu jest pokazana w widoku, czy nie. Aby korzystać z opcji wydajności lub tylko pionowych slajdów, nie używaj tego! Ten kod jest bardziej skuteczny w rysowaniu przypadków.


1

Lepsze rozwiązanie:

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


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

12
Powinieneś spróbować wyjaśnić, dlaczego Twoja wersja jest lepsza. Na obecnym etapie wygląda mniej więcej tak samo jak inne rozwiązania.
Andy E

1
Świetne rozwiązanie, ma BOX / ScrollContainer i nie używa WINDOW (tylko jeśli nie jest określony). Spójrz na kod niż oceń, jest to bardziej uniwersalne rozwiązanie (często go
szukałem

1

Tak proste, jak to tylko możliwe, IMO:

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

0

Oto funkcja, która informuje, czy element jest widoczny w bieżącej rzutni elementu nadrzędnego :

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

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

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

0

Sprawdza, czy element jest przynajmniej częściowo widoczny (wymiar pionowy):

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

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


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

0

Miałem to samo pytanie i rozgryzłem je za pomocą getBoundingClientRect ().

Ten kod jest całkowicie „ogólny” i musi być napisany tylko raz, aby zadziałał (nie musisz go pisać dla każdego elementu, który chcesz wiedzieć, jest w okienku ekranu).

Ten kod sprawdza tylko, czy jest pionowo w rzutni, a nie w poziomie . W tym przypadku „elementy” zmiennej (tablicy) przechowują wszystkie elementy, które sprawdzasz, aby były pionowo w rzutni, więc chwyć dowolne elementy w dowolnym miejscu i przechowuj je tam.

„Pętla for” zapętla każdy element i sprawdza, czy znajduje się on pionowo w rzutni. Ten kod jest wykonywany za każdym razem, gdy użytkownik przewija! Jeśli getBoudingClientRect (). Top ma mniej niż 3/4 rzutni (element ma jedną czwartą w rzutni), rejestruje się jako „w rzutni”.

Ponieważ kod jest ogólny, będziesz chciał wiedzieć, który element znajduje się w rzutni. Aby się tego dowiedzieć, możesz to ustalić na podstawie niestandardowego atrybutu, nazwy węzła, identyfikatora, nazwy klasy i innych kryteriów.

Oto mój kod (powiedz mi, jeśli nie działa; został przetestowany w Internet Explorer 11, Firefox 40.0.3, Chrome Wersja 45.0.2454.85 m, Opera 31.0.1889.174 i Edge z Windows 10, [jeszcze nie Safari ]) ...

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

0

To proste i małe rozwiązanie, które zadziałało dla mnie.

Przykład : Chcesz sprawdzić, czy element jest widoczny w elemencie nadrzędnym, który ma przewijanie przepełnienia.

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

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

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

0

Wszystkie odpowiedzi tutaj określają, czy element jest całkowicie zawarty w rzutni, a nie tylko w jakiś sposób widoczny. Na przykład, jeśli tylko połowa obrazu jest widoczna u dołu widoku, rozwiązania tutaj zawiodą, biorąc pod uwagę, że „na zewnątrz”.

Miałem przypadek użycia, w którym ładuję się leniwie IntersectionObserver, ale z powodu animacji pojawiających się podczas wyskakującego okienka nie chciałem oglądać żadnych obrazów, które były już przecinane podczas ładowania strony. Aby to zrobić, użyłem następującego kodu:

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

Jest to w zasadzie sprawdzenie, czy górna lub dolna granica jest niezależnie w rzutni. Przeciwny koniec może znajdować się na zewnątrz, ale dopóki jeden jest w środku, jest „widoczny” co najmniej częściowo.


-1

Korzystam z tej funkcji (sprawdza tylko, czy y jest niewidoczne, ponieważ przez większość czasu x nie jest potrzebne)

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

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

}

-3

W przypadku podobnego wyzwania bardzo podobała mi się ta treść, która ujawnia wypełnienie dla scrollIntoViewIfNeeded () .

Wszystkie niezbędne Kung Fu potrzebne do odpowiedzi znajdują się w tym bloku:

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;

thisodnosi się do elementu, który chcesz wiedzieć, jeśli jest, na przykład, overToplub overBottom- powinieneś dostać dryf ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.