Pertanyaan Bagaimana cara mengetahui apakah elemen DOM terlihat di viewport saat ini?


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

(Pertanyaannya tentang Firefox)


747
2017-09-23 21:24


asal


Jawaban:


Memperbarui: Waktu terus berjalan dan begitu juga browser kami. Teknik ini tidak lagi direkomendasikan dan Anda harus menggunakan solusi @ Dan di bawah ini (https://stackoverflow.com/a/7557433/5628) jika Anda tidak perlu mendukung IE <7.

Solusi orisinal (sekarang sudah ketinggalan zaman):

Ini akan memeriksa apakah elemen 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 dari 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
  );
}

286
2017-09-24 02:40



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

Solusi yang dipilih sebagai yang benar adalah hampir tidak pernah tepat. Kamu bisa Baca lebih banyak tentang bugnya.


Solusi ini diuji pada IE7 +, iOS5 + Safari, Android2 +, Blackberry, Opera Mobile, dan IE Mobile 10.


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

Bagaimana cara menggunakan:

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

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

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

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


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

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

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

Pedoman dan perangkap umum:

Mungkin Anda perlu melacak zoom halaman / pinch perangkat seluler? jQuery harus ditangani zoom / cubit browser silang, sebaliknya pertama atau kedua tautan akan membantu Anda.

Jika kamu ubah DOM, dapat mempengaruhi visibilitas elemen. Anda harus mengendalikan itu dan menelepon handler() secara manual. Sayangnya, kami tidak memiliki peramban silang onrepaint peristiwa. Di sisi lain yang memungkinkan kami melakukan pengoptimalan dan melakukan pemeriksaan ulang hanya pada modifikasi DOM yang dapat mengubah visibilitas elemen.

Tidak akan pernah gunakan di dalam jQuery $ (dokumen) .ready () hanya karena tidak ada jaminan CSS yang telah diterapkan pada saat ini. Kode Anda dapat bekerja secara lokal dengan CSS Anda di hard drive, tetapi setelah diletakkan di server jarak jauh ia akan gagal.

Setelah DOMContentLoaded dipecat, gaya diterapkan, tapi gambar belum dimuat. Jadi, kita harus tambahkan window.onload pendengar acara.

Kami belum dapat menangkap acara zoom / pinch.

Jalan terakhir bisa menjadi kode berikut:

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

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

TODO: metode ini tidak menangani dua situasi:


1163
2018-03-04 14:18



Memperbarui

Di browser modern, Anda mungkin ingin memeriksa API Pengamat Persimpangan yang memberikan manfaat berikut:

  • Performa yang lebih baik daripada mendengarkan acara gulir
  • Bekerja di iframes lintas domain
  • Dapat mengetahui apakah suatu elemen menghalangi / memotong yang lain

Intersection Observer sedang dalam perjalanan untuk menjadi standar yang lengkap dan sudah didukung di Chrome 51+, Edge 15+ dan Firefox 55+ dan sedang dalam pengembangan untuk Safari. Ada juga a polyfill tersedia.


Jawaban sebelumnya

Ada beberapa masalah dengan jawaban yang diberikan oleh 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 positif palsu untuk elemen yang:

  • Tersembunyi oleh elemen lain di depan yang sedang diuji
  • Di luar area yang terlihat dari orangtua atau elemen leluhur
  • Suatu elemen atau anak-anaknya disembunyikan dengan menggunakan CSS clip milik

Keterbatasan ini ditunjukkan dalam hasil a tes sederhana:

Failed test, using isElementInViewport

Solusinya: isElementVisible()

Berikut ini solusi untuk masalah tersebut, dengan hasil pengujian di bawah dan penjelasan tentang beberapa bagian kode.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

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

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

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

Dan hasilnya:

Passed test, using isElementVisible

Catatan tambahan

Metode ini bukan tanpa keterbatasannya sendiri. Misalnya, elemen yang diuji dengan indeks z yang lebih rendah daripada elemen lain di lokasi yang sama akan diidentifikasi sebagai tersembunyi bahkan jika elemen di depan tidak benar-benar menyembunyikan bagian apa pun darinya. Namun, metode ini memiliki kegunaannya dalam beberapa kasus yang solusi Dan tidak mencakup.

Kedua element.getBoundingClientRect() dan document.elementFromPoint() adalah bagian dari spesifikasi Draft Kerja CSSOM dan didukung setidaknya pada IE 6 dan yang lebih baru dan paling 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 dikembalikan oleh document.elementFromPoint() adalah simpul anak dari elemen yang kami uji untuk visibilitas. Ini juga mengembalikan nilai true jika elemen yang dikembalikan adalah elemen yang sama. Ini hanya membuat cek lebih kuat. Ini didukung di semua browser utama, Firefox 9.0 menjadi yang terakhir dari mereka untuk 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% ―itu tidak akan membutuhkan banyak untuk menyesuaikan bagian terakhir dari jawaban. Namun, perhatikan bahwa mungkin akan sangat lambat jika Anda memeriksa setiap piksel untuk memastikannya 100% terlihat.


120
2018-04-29 02:36



Saya mencoba Dan jawabannya  tapi aljabar yang digunakan untuk menentukan batasan tidak benar. jawaban ryanve lebih dekat, tetapi elemen yang sedang diuji harus berada di dalam viewport oleh setidaknya 1 piksel, jadi cobalah fungsi 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() */;
}

43
2018-05-03 17:00



Ada jQuery bernama plugin dalam penglihatan yang melakukan pekerjaan itu


25
2017-09-25 12:54



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

Ngomong-ngomong, itu perbedaan antara window.innerWidth dan document.documentElement.clientWidth adalah clientWidth / clientHeight yang tidak menyertakan 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>

25
2017-09-14 05:49



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

}

Pengembalian true jika apa saja bagian dari elemen ada di viewport.


23
2017-08-02 13:40