Bagaimana saya bisa tahu jika elemen DOM terlihat di viewport saat ini?

950

Apakah ada cara yang efisien untuk mengetahui apakah elemen DOM (dalam dokumen HTML) saat ini terlihat (muncul di viewport )?

(Pertanyaannya mengacu pada Firefox.)

benzaita
sumber
Tergantung apa yang Anda maksud dengan terlihat. Jika yang Anda maksud adalah saat ini ditampilkan pada halaman, diberi posisi gulir, Anda dapat menghitungnya berdasarkan elemen y offset dan posisi gulir saat ini.
roryf
18
Klarifikasi: Terlihat, seperti di dalam persegi panjang yang saat ini ditampilkan. Terlihat, seperti tidak disembunyikan atau ditampilkan: tidak ada? Terlihat, seperti tidak di belakang sesuatu yang lain di Z-order?
Adam Wright
6
seperti di dalam persegi panjang yang saat ini ditampilkan. Terima kasih. Diulang
benzaita
2
Perhatikan bahwa semua jawaban di sini akan memberi Anda positif palsu untuk item yang berada di luar area terlihat elemen induknya (eh dengan luapan tersembunyi) tetapi di dalam area viewport.
Andy E
1
Ada satu juta jawaban dan sebagian besar panjangnya sangat panjang. Lihat di sini untuk liner dua tingkat
leonheess

Jawaban:

347

Pembaruan: Waktu berjalan terus dan begitu juga browser kami. Teknik ini tidak lagi direkomendasikan dan Anda harus menggunakan solusi Dan jika Anda tidak perlu mendukung versi Internet Explorer sebelum 7.

Solusi asli (sekarang kedaluwarsa):

Ini akan memeriksa apakah elemen tersebut sepenuhnya terlihat di viewport saat ini:

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

Anda dapat memodifikasi ini hanya untuk menentukan apakah ada bagian elemen yang terlihat di viewport:

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
sumber
1
Fungsi asli yang diposting memiliki kesalahan. Diperlukan untuk menyimpan lebar / tinggi sebelum menetapkan ulang ...
Prestaul
15
Bagaimana jika elemen tersebut hidup dalam div yang dapat digulir dan digulir ke luar dari tampilan ??
amartynov
2
Harap tinjau versi terbaru dari skrip di bawah ini
Dan
1
Juga ingin tahu tentang pertanyaan @ amartynov. Adakah yang tahu bagaimana cara mengetahui apakah suatu elemen disembunyikan karena melimpahnya unsur leluhur? Bonus jika ini dapat dideteksi terlepas dari seberapa dalam sarangnya anak itu.
Eric Nguyen
1
@deadManN berulang melalui DOM sangat lambat. Itu alasan yang cukup, tetapi vendor peramban juga telah menciptakan getBoundingClientRectsecara spesifik tujuan menemukan koordinat elemen ... Mengapa kita tidak menggunakannya?
Prestaul
1402

Sekarang sebagian besar browser mendukung metode getBoundingClientRect , yang telah menjadi praktik terbaik. Menggunakan jawaban lama sangat lambat , tidak akurat dan memiliki beberapa bug .

Solusi yang dipilih sebagai benar hampir tidak pernah tepat . Anda dapat membaca lebih lanjut tentang bug-nya.


Solusi ini diuji pada Internet Explorer 7 (dan yang lebih baru), iOS 5 (dan yang lebih baru) Safari, Android 2.0 (Eclair) dan yang lebih baru, BlackBerry, Opera Mobile, dan 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() */
    );
}

Cara Penggunaan:

Anda dapat yakin bahwa fungsi yang diberikan di atas mengembalikan jawaban yang benar pada saat dipanggil, tetapi bagaimana dengan visibilitas elemen pelacakan sebagai suatu peristiwa?

Tempatkan kode berikut di bagian bawah <body>tag Anda :

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

Jika Anda melakukan modifikasi DOM, mereka dapat mengubah visibilitas elemen Anda tentu saja.

Panduan dan perangkap umum:

Mungkin Anda perlu melacak halaman zoom / cubit perangkat seluler? jQuery harus menangani zoom / pinch cross browser, jika tidak, pertama atau kedua tautan akan membantu Anda.

Jika Anda memodifikasi DOM , ini dapat memengaruhi visibilitas elemen. Anda harus mengambil kendali atas itu dan menelepon handler()secara manual. Sayangnya, kami tidak memiliki browser lintasonrepaint acara . Di sisi lain yang memungkinkan kami melakukan optimasi dan melakukan pengecekan ulang hanya pada modifikasi DOM yang dapat mengubah visibilitas elemen.

Never Ever menggunakannya di dalam jQuery $ (document) .ready () saja, karena tidak ada jaminan CSS telah diterapkan pada saat ini. Kode Anda dapat bekerja secara lokal dengan CSS Anda pada hard drive, tetapi sekali memakai server jauh itu akan gagal.

Setelah DOMContentLoadeddipecat, gaya diterapkan , tetapi gambar belum dimuat . Jadi, kita harus menambahkan window.onloadpendengar acara.

Kami belum dapat menangkap acara zoom / pinch.

Pilihan terakhir bisa berupa kode berikut:

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

Anda dapat menggunakan pageVisibiliy fitur hebat dari API HTML5 jika Anda peduli jika tab dengan halaman web Anda aktif dan terlihat.

TODO: metode ini tidak menangani dua situasi:

Dan
sumber
6
Saya menggunakan solusi ini (berhati-hatilah terhadap kesalahan ketik "botom"). Ada juga sesuatu yang harus diperhatikan, ketika elemen yang kita pertimbangkan akan memiliki gambar di dalamnya. Chrome (setidaknya) harus menunggu gambar dimuat agar memiliki nilai tepat untuk boundingRectangle. Tampaknya Firefox tidak memiliki "masalah" ini
Claudio
4
Apakah itu berfungsi ketika Anda mengaktifkan scrolling di wadah di dalam tubuh. Misalnya tidak berfungsi di sini - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele hadir di dalam wadah yang memiliki scrolling diaktifkan.
agaase
70
Perhitungan mengasumsikan bahwa elemen lebih kecil dari layar. Jika Anda memiliki elemen tinggi atau lebar, mungkin lebih akurat untuk digunakanreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan
11
Kiat: Bagi mereka yang mencoba menerapkan ini dengan jQuery, hanya pengingat yang ramah untuk meneruskan objek HTML DOM (misalnya, isElementInViewport(document.getElementById('elem'))) dan bukan objek jQuery (misalnya, isElementInViewport($("#elem))). Setara jQuery adalah menambahkan[0] seperti: isElementInViewport($("#elem)[0]).
Jimin
11
el is not defined
M_Willett
176

Memperbarui

Di browser modern, Anda mungkin ingin memeriksa API Pengamat titik - temu yang memberikan manfaat berikut:

  • Performa yang lebih baik daripada mendengarkan acara gulir
  • Bekerja di iframe lintas domain
  • Dapat mengetahui apakah suatu elemen menghalangi / berpotongan lainnya

Intersection Observer sedang dalam perjalanan untuk menjadi standar penuh dan sudah didukung di Chrome 51+, Edge 15+ dan Firefox 55+ dan sedang dikembangkan untuk Safari. Tersedia juga polyfill .


Jawaban sebelumnya

Ada beberapa masalah dengan jawaban yang diberikan Dan yang mungkin membuatnya menjadi pendekatan yang tidak cocok untuk beberapa situasi. Beberapa masalah ini ditunjukkan dalam jawabannya di dekat bagian bawah, bahwa kodenya akan memberikan hasil positif palsu untuk elemen yang:

  • Tersembunyi oleh elemen lain di depan yang sedang diuji
  • Di luar area yang terlihat elemen induk atau leluhur
  • Elemen atau anak-anaknya disembunyikan dengan menggunakan clipproperti CSS

Batasan ini ditunjukkan dalam hasil tes sederhana berikut :

Tes gagal, menggunakan isElementInViewport

Solusinya: isElementVisible()

Inilah solusi untuk masalah-masalah tersebut, dengan hasil tes di bawah ini dan penjelasan dari beberapa bagian kode

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

Lulus tes: http://jsfiddle.net/AndyE/cAY8c/

Dan hasilnya:

Lulus tes, menggunakan isElementVisible

Catatan tambahan

Namun metode ini bukan tanpa batasannya sendiri. Misalnya, elemen yang diuji dengan indeks-z lebih rendah daripada elemen lain di lokasi yang sama akan diidentifikasi sebagai disembunyikan bahkan jika elemen di depan tidak benar-benar menyembunyikan bagiannya. Namun, metode ini memiliki kegunaan dalam beberapa kasus yang tidak dicakup solusi Dan.

Keduanya element.getBoundingClientRect()dan document.elementFromPoint()merupakan bagian dari spesifikasi Draft Kerja CSSOM dan didukung di setidaknya IE 6 dan yang lebih baru dan sebagian besar browser desktop untuk waktu yang lama (meskipun, tidak sempurna). Lihat Quirksmode pada fungsi-fungsi ini untuk informasi lebih lanjut.

contains()digunakan untuk melihat apakah elemen yang dikembalikan oleh document.elementFromPoint()adalah simpul anak dari elemen yang kami uji untuk visibilitas. Ini juga mengembalikan true jika elemen yang dikembalikan adalah elemen yang sama. Ini hanya membuat pemeriksaan lebih kuat. Ini didukung di semua browser utama, Firefox 9.0 menjadi yang terakhir yang menambahkannya. Untuk dukungan Firefox yang lebih lama, periksa riwayat jawaban ini.

Jika Anda ingin menguji lebih banyak poin di sekitar elemen untuk visibilitas ― yaitu, untuk memastikan elemen tidak tercakup oleh lebih dari, katakanlah, 50% ― tidak perlu banyak untuk menyesuaikan bagian terakhir dari jawaban. Namun, perlu diketahui bahwa mungkin akan sangat lambat jika Anda memeriksa setiap piksel untuk memastikan itu 100% terlihat.

Andy E
sumber
2
Apakah Anda bermaksud menggunakan doc.documentElement.clientWidth? Haruskah itu menjadi 'document.documentElement' sebagai gantinya? Pada catatan yang berbeda, ini adalah satu-satunya metode yang juga berfungsi untuk kasus penggunaan seperti menyembunyikan konten elemen untuk aksesibilitas menggunakan properti 'clip' CSS: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping
@ klamping: tangkapan yang bagus, terima kasih. Saya telah menyalinnya langsung dari kode tempat saya menggunakan docsebagai alias untuk document. Ya, saya suka menganggap ini sebagai solusi yang layak untuk kasus tepi.
Andy E
2
Bagi saya itu tidak berfungsi. Tetapi inViewport () pada jawaban sebelumnya berfungsi di FF.
Satya Prakash
9
Mungkin juga bermanfaat untuk memeriksa bahwa pusat elemen terlihat jika Anda memiliki sudut bundar atau transformasi yang diterapkan, karena sudut yang terikat mungkin tidak mengembalikan elemen yang diharapkan:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared
2
Tidak bekerja pada input untuk saya (chrome canary 50). Tidak yakin mengapa, mungkin sudut bulat asli? Saya harus mengurangi sedikit coords untuk membuatnya berfungsi el.contains (efp (rect.left + 1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.bottom-1)) || el.contains (efp (rect.left + 1, rect.bottom-1))
Rayjax
65

Saya mencoba jawaban Dan . Namun , aljabar yang digunakan untuk menentukan batas berarti bahwa elemen tersebut harus berukuran ≤ ukuran viewport dan sepenuhnya berada di dalam viewport true, sehingga mudah mengarah ke negatif palsu. Jika Anda ingin menentukan apakah suatu elemen ada di viewport sama sekali, jawaban ryanve dekat, tetapi elemen yang diuji harus tumpang tindih dengan viewport, jadi coba ini:

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

Sebagai layanan publik:
Jawaban Dan dengan perhitungan yang benar (elemen dapat> jendela, terutama pada layar ponsel), dan pengujian jQuery yang benar, serta menambahkan isElementPartiallyInViewport:

By the way, perbedaan antara window.innerWidth dan document.documentElement.clientWidth adalah bahwa clientWidth / clientHeight tidak termasuk scrollbar, sedangkan window.innerWidth / Height tidak.

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

Kasus cobaan

<!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
sumber
2
isElementPartiallyInViewportsangat berguna juga. Bagus
RJ Cuthbertson
Untuk isElementInViewport (e): Kode Anda bahkan tidak memuat gambar, Yang ini benar: fungsi isElementInViewport (e) {var t = e.getBoundingClientRect (); kembalikan 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: Tidak ada kode saya yang memuat gambar, jadi mengapa harus demikian, dan rumusnya benar.
Stefan Steiger
1
@targumon: Alasannya adalah dukungan dari browser lama.
Stefan Steiger
1
@StefanSteiger menurut MDN didukung sejak IE9 sehingga praktis aman (setidaknya dalam kasus saya) hanya menggunakan window.innerHeight secara langsung. Terima kasih!
targum
28

Lihat sumber ambang , yang menggunakan getBoundingClientRect . Itu seperti:

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

}

Itu kembali truejika ada bagian dari elemen di viewport.

ryanve
sumber
27

Versi saya yang lebih pendek dan lebih cepat:

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

Dan jsFiddle sesuai kebutuhan: https://jsfiddle.net/on1g619L/1/

Eric Chen
sumber
4
Solusi saya lebih serakah dan lebih cepat, ketika elemen memiliki piksel dalam viewport, itu akan kembali salah.
Eric Chen
1
Saya suka itu. Ringkas. Anda bisa menghapus spasi antara nama fungsi dan tanda kurung, dan antara tanda kurung dan tanda kurung, pada baris pertama. Tidak pernah menyukai ruang itu. Mungkin hanya editor Teks saya yang memberi kode warna semua yang masih membuatnya mudah dibaca. function aaa (arg) {pernyataan} Saya tahu itu tidak membuatnya mengeksekusi lebih cepat, termasuk dalam minifying.
YK
21

Saya merasa bermasalah bahwa tidak ada versi jQuery- sentris dari fungsi yang tersedia. Ketika saya menemukan solusi Dan, saya memata-matai kesempatan untuk menyediakan sesuatu untuk orang-orang yang suka memprogram dalam gaya jQuery OO. Sangat bagus dan tajam dan bekerja seperti pesona bagi saya.

Bada bing bada booming

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

Pemakaian

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

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});
r3wt
sumber
1
Apakah kurung kurawal cukup untuk menutup pernyataan if?
calasyr
1
Saya tidak bisa membuatnya bekerja dengan banyak elemen dari kelas yang identik.
The Whiz of Oz
1
@TheWhizofOz saya telah memperbarui jawaban saya untuk memberikan contoh-contoh kasus penggunaan lain yang mungkin telah Anda kemukakan. semoga berhasil.
r3wt
10

API Pengamat Titik- Temu yang baru menjawab pertanyaan ini dengan sangat langsung.

Solusi ini akan memerlukan polyfill karena Safari, Opera dan Internet Explorer belum mendukung ini (polyfill termasuk dalam solusi).

Dalam solusi ini, ada kotak di luar pandangan yang menjadi target (diamati). Ketika muncul, tombol di bagian atas di header disembunyikan. Ini ditampilkan setelah kotak meninggalkan tampilan.

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
sumber
3
Implementasi yang baik, dan menurut tautan dalam jawaban ini, ia harus bekerja di safari dengan menambahkan <!DOCTYPE html>ke HTML
Alon Eitan
Perhatikan bahwa IntersectionObserverini adalah fitur eksperimental (yang dapat berubah di masa mendatang).
Karthik Chintala
2
@KarthikChintala - ini didukung di setiap browser kecuali IE - dan ada juga polyfill yang tersedia.
Randy Casburn
Tidak menjawab pertanyaan OP karena hanya mendeteksi perubahan : IntersectionObserverhanya mengaktifkan callback setelah perpindahan target relatif ke root.
Brandon Hill
Ketika memanggil observeacara dipecat segera memberi tahu Anda saat ini persimpangan elemen dilacak. Jadi, dalam beberapa cara - itu alamat.
Mesqalito
8

Semua jawaban yang saya temui di sini hanya memeriksa apakah elemen diposisikan di dalam viewport saat ini . Tetapi itu tidak berarti bahwa itu terlihat .
Bagaimana jika elemen yang diberikan ada di dalam div dengan konten yang meluap, dan itu tidak terlihat?

Untuk mengatasinya, Anda harus memeriksa apakah elemen tersebut terkandung oleh semua orang tua.
Solusi saya melakukan hal itu:

Ini juga memungkinkan Anda menentukan berapa banyak elemen yang harus terlihat.

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

Solusi ini mengabaikan fakta bahwa elemen mungkin tidak terlihat karena fakta lain, seperti opacity: 0.

Saya telah menguji solusi ini di Chrome dan Internet Explorer 11.

Domysee
sumber
8

Saya menemukan bahwa jawaban yang diterima di sini terlalu rumit untuk kebanyakan kasus penggunaan. Kode ini melakukan pekerjaan dengan baik (menggunakan jQuery) dan membedakan antara elemen yang sepenuhnya terlihat dan sebagian terlihat:

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
sumber
Anda harus melakukan cache di $window = $(window)luar scroll handler.
sam
4

Solusi paling sederhana sebagai dukungan Element.getBoundingClientRect () telah menjadi sempurna :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}
kesendirian
sumber
Bagaimana hal ini berlaku di browser seluler? Kebanyakan dari mereka buggy mengenai viewport, dengan header mereka naik atau turun pada scroll, dan perilaku yang berbeda ketika keyboard muncul, tergantung apakah itu android atau ios, dll.
Kev
@Kev Seharusnya berfungsi dengan baik tergantung pada saat Anda memanggil metode ini. Jika Anda memanggilnya lalu mengubah ukuran jendela, hasilnya mungkin tidak lagi benar. Anda dapat menyebutnya di setiap acara ukuran tergantung pada jenis fungsi yang Anda inginkan. Jangan ragu untuk mengajukan pertanyaan terpisah tentang kasus penggunaan khusus Anda dan ping saya di sini.
leonheess
3

Saya pikir ini adalah cara yang lebih fungsional untuk melakukannya. Jawaban Dan tidak bekerja dalam konteks rekursif.

Fungsi ini memecahkan masalah ketika elemen Anda berada di dalam div yang lain dapat digulir dengan menguji setiap level secara rekursif hingga tag HTML, dan berhenti pada false pertama.

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

Jawaban yang paling diterima tidak berfungsi saat memperbesar Google Chrome di Android. Dalam kombinasi dengan jawaban Dan , untuk menjelaskan Chrome di Android, visualViewport harus digunakan. Contoh berikut hanya mengambil pemeriksaan vertikal ke akun dan menggunakan jQuery untuk ketinggian jendela:

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

Inilah solusi saya. Ini akan berfungsi jika suatu elemen disembunyikan di dalam wadah yang dapat digulir.

Ini demo (coba ubah ukuran jendela menjadi)

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

Saya hanya perlu memeriksa apakah itu terlihat di sumbu Y (untuk fitur Ajax scroll-more-records scrolling).

Sekutu
sumber
2

Berdasarkan solusi Dan , saya harus membersihkan implementasi sehingga menggunakannya berkali-kali pada halaman yang sama lebih mudah:

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

});

Cara saya menggunakannya adalah ketika elemen bergulir ke tampilan, saya menambahkan kelas yang memicu animasi keyframe CSS. Ini sangat mudah dan bekerja dengan sangat baik ketika Anda memiliki lebih dari 10 hal untuk dikondisikan secara kondisional pada sebuah halaman.

Pirijan
sumber
Anda harus melakukan cache di $window = $(window)luar scroll handler
Adam Rehal
2

Sebagian besar penggunaan dalam jawaban sebelumnya gagal pada titik-titik ini:

-Ketika setiap piksel elemen terlihat, tetapi bukan " sudut ",

-Ketika elemen lebih besar dari viewport dan terpusat ,

-Sebagian besar dari mereka hanya memeriksa elemen tunggal di dalam dokumen atau jendela .

Nah, untuk semua masalah ini saya punya solusi dan sisi positifnya adalah:

-Anda dapat kembali visibleketika hanya piksel dari sisi mana pun yang muncul dan bukan sudut,

-Anda masih dapat kembali visiblesementara elemen lebih besar dari viewport,

-Anda dapat memilih parent elementatau secara otomatis membiarkannya memilih,

-Bekerja pada elemen yang ditambahkan secara dinamis juga.

Jika Anda memeriksa cuplikan di bawah ini, Anda akan melihat perbedaan dalam menggunakan overflow-scrollwadah elemen tidak akan menimbulkan masalah dan melihat bahwa tidak seperti jawaban lain di sini bahkan jika piksel muncul dari sisi mana pun atau ketika suatu elemen lebih besar dari viewport dan kami melihat bagian dalamnya piksel elemen yang masih berfungsi.

Penggunaannya sederhana:

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

Demonstrasi tanpa crossSearchAlgorithmyang berguna untuk elemen yang lebih besar dari viewport check element3 piksel dalam untuk melihat:

Anda lihat, ketika Anda berada di dalam elemen3 itu gagal untuk mengetahui apakah itu terlihat atau tidak, karena kami hanya memeriksa apakah elemen tersebut terlihat dari sisi atau sudut .

Dan ini termasuk crossSearchAlgorithmyang memungkinkan Anda untuk tetap kembali visibleketika elemen lebih besar dari viewport:

JSFiddle untuk dimainkan: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Kode ini dibuat untuk informasi yang lebih tepat jika ada bagian elemen yang ditampilkan dalam tampilan atau tidak. Untuk opsi kinerja atau hanya slide vertikal, jangan gunakan ini! Kode ini lebih efektif dalam menggambar kasus.

Berker Yüceer
sumber
1

Solusi yang lebih baik:

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
sumber
12
Anda harus mencoba dan menjelaskan mengapa versi Anda lebih baik. Seperti berdiri, itu terlihat kurang lebih sama dengan solusi lainnya.
Andy E
1
Solusi hebat ini memiliki BOX / ScrollContainer dan tidak menggunakan WINDOW (hanya jika tidak ditentukan). Lihatlah kode daripada menilai itu adalah solusi yang lebih universal (Sedang banyak mencari itu)
teter
1

Sesederhana mungkin, IMO:

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

Berikut adalah fungsi yang memberi tahu jika suatu elemen terlihat di viewport saat ini dari elemen induk :

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

Ini memeriksa apakah suatu elemen setidaknya sebagian terlihat (dimensi vertikal):

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

Saya memiliki pertanyaan yang sama dan menemukan jawabannya dengan menggunakan getBoundingClientRect ().

Kode ini sepenuhnya 'generik' dan hanya perlu ditulis satu kali agar bisa berfungsi (Anda tidak harus menuliskannya untuk setiap elemen yang ingin Anda ketahui ada di viewport).

Kode ini hanya memeriksa untuk melihat apakah itu secara vertikal di viewport, bukan horizontal . Dalam hal ini, 'elemen' variabel (array) menampung semua elemen yang Anda periksa secara vertikal di viewport, jadi ambil elemen apa pun yang Anda inginkan di mana saja dan simpan di sana.

'For loop', loop melalui setiap elemen dan memeriksa untuk melihat apakah itu secara vertikal di viewport. Kode ini dijalankan setiap kali pengguna menggulir! Jika getBoudingClientRect (). Top kurang dari 3/4 viewport (elemennya adalah seperempat di viewport), ia terdaftar sebagai 'di viewport'.

Karena kodenya generik, Anda ingin tahu elemen 'mana' yang ada di viewport. Untuk mengetahuinya, Anda dapat menentukannya dengan atribut khusus, nama simpul, id, nama kelas, dan banyak lagi.

Ini kode saya (beri tahu saya jika tidak berfungsi; sudah diuji di Internet Explorer 11, Firefox 40.0.3, Versi Chrome 45.0.2454.85 m, Opera 31.0.1889.174, dan Edge dengan Windows 10, [belum Safari belum ]) ...

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

Ini adalah solusi mudah dan kecil yang berhasil bagi saya.

Contoh : Anda ingin melihat apakah elemen terlihat di elemen induk yang memiliki gulir melimpah.

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

Semua jawaban di sini menentukan apakah elemen tersebut sepenuhnya terkandung dalam viewport, tidak hanya terlihat dalam beberapa cara. Misalnya, jika hanya setengah dari gambar terlihat di bagian bawah tampilan, solusi di sini akan gagal, mengingat "di luar".

Saya memiliki use case di mana saya melakukan pemuatan malas melalui IntersectionObserver, tetapi karena animasi yang terjadi selama pop-in, saya tidak ingin mengamati gambar yang sudah berpotongan pada pemuatan halaman. Untuk melakukan itu, saya menggunakan kode berikut:

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

Ini pada dasarnya memeriksa untuk melihat apakah bagian atas atau bawah terikat secara independen di viewport. Ujung yang berlawanan mungkin berada di luar, tetapi selama salah satu ujung berada, itu "terlihat" setidaknya sebagian.

Chris Pratt
sumber
-1

Saya menggunakan fungsi ini (ini hanya memeriksa apakah y adalah inscreen karena sebagian besar waktu x tidak diperlukan)

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

Untuk tantangan serupa, saya sangat menikmati inti ini yang memperlihatkan polyfill untuk scrollIntoViewIfNeeded () .

Semua Kung Fu yang diperlukan untuk menjawab ada di dalam blok ini:

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;

thismengacu pada elemen yang Anda ingin tahu apakah itu, misalnya, overTopatau overBottom- Anda hanya perlu mengetahui ...

Philzen
sumber