ค้นหาเว็บไซต์

javascript - ฉันจะบอกได้อย่างไรว่าส่วนประกอบ DOM ปรากฏใน viewport ปัจจุบันหรือไม่?


มีวิธีที่มีประสิทธิภาพในการตรวจสอบว่าองค์ประกอบ DOM (ในเอกสาร HTML) ปรากฏอยู่บนหน้าต่างมุมมอง (viewport) หรือไม่หรือไม่?

(คำถามนี้เกี่ยวกับ Firefox)

คำตอบ #1

ตอนนี้เบราว์เซอร์ส่วนใหญ่รองรับวิธี getBoundingClientRect ซึ่งกลายเป็นแนวทางปฏิบัติที่ดีที่สุด การใช้คำตอบเก่านั้นช้ามาก ไม่แม่นยำ และมีบั๊กหลายอย่าง

วิธีแก้ปัญหาที่เลือกว่าถูกต้องเกือบจะไม่เคยแม่นยำเลย


วิธีแก้ปัญหานี้ได้รับการทดสอบบน Internet Explorer 7 (และรุ่นที่ใหม่กว่า), iOS 5 (และรุ่นที่ใหม่กว่า) Safari, Android 2.0 (Eclair) และรุ่นที่ใหม่กว่า, BlackBerry, Opera Mobile และ 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() */
    );
}

วิธีใช้:

คุณสามารถมั่นใจได้ว่าฟังก์ชันที่ให้มาข้างต้นจะส่งค่าคำตอบที่ถูกต้องในเวลาที่มันถูกเรียก แต่แล้วการติดตามการมองเห็นขององค์ประกอบเป็นเหตุการณ์ล่ะ?

วางโค้ดต่อไปนี้ที่ด้านล่างของแท็ก <body> ของคุณ:

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

ถ้าคุณทำการแก้ไข DOM ใด ๆ แน่นอนว่ามันสามารถเปลี่ยนการมองเห็นขององค์ประกอบของคุณได้

แนวทางและข้อพลาดทั่วไป:

บางทีคุณอาจต้องติดตามการซูมหน้า/การหยิกบนอุปกรณ์มือถือ? jQuery ควรจัดการการซูม/หยิกข้ามเบราว์เซอร์ได้ หากไม่เช่นนั้น ลิงก์แรกหรือที่สองควรช่วยคุณได้

ถ้าคุณ แก้ไข DOM มันสามารถส่งผลต่อการมองเห็นขององค์ประกอบได้ คุณควรควบคุมสิ่งนั้นและเรียก handler() ด้วยตัวเอง น่าเสียดายที่เรายังไม่มีเหตุการณ์ onrepaint ข้ามเบราว์เซอร์ อย่างไรก็ตาม สิ่งนั้นทำให้เราสามารถทำการปรับแต่งประสิทธิภาพและตรวจสอบซ้ำเฉพาะการแก้ไข DOM ที่สามารถเปลี่ยนการมองเห็นขององค์ประกอบได้

อย่าใช้เด็ดขาด ภายใน jQuery $ (document).ready() เท่านั้น เพราะไม่มีการรับประกันว่า CSS จะถูกนำไปใช้ในช่วงเวลานี้ โค้ดของคุณอาจทำงานได้ในเครื่องของคุณพร้อมกับ CSS บนฮาร์ดไดรฟ์ แต่เมื่อวางบนเซิร์ฟเวอร์ระยะไกล มันจะล้มเหลว

หลังจากที่ DOMContentLoaded ทำงานแล้ว สไตล์จะถูกนำไปใช้ แต่ภาพยังไม่ได้ถูกโหลด ดังนั้นเราควรเพิ่มตัวฟังเหตุการณ์ window.onload

เรายังไม่สามารถจับเหตุการณ์ซูม/บีบได้

ทางออกสุดท้ายอาจเป็นโค้ดดังต่อไปนี้:

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

คุณสามารถใช้คุณสมบัติที่ยอดเยี่ยม pageVisibility ของ HTML5 API ได้หากคุณสนใจว่าคำสั่งแท็บที่มีหน้าเว็บของคุณนั้นกำลังใช้งานและสามารถมองเห็นได้หรือไม่

TODO: วิธีนี้ไม่ได้จัดการกับสองสถานการณ์:

  • การซ้อนทับโดยใช้ z-index
  • การใช้ overflow-scroll ในคอนเทนเนอร์ขององค์ประกอบ
  • ลองอะไรใหม่ ๆ - อธิบาย API ของ Intersection Observer.

คำตอบ #2

อัปเดต: เวลาผ่านไปและเบราว์เซอร์ของเราก็เช่นกัน เทคนิคนี้ไม่แนะนำอีกต่อไป และคุณควรใช้วิธีแก้ของแดนหากคุณไม่จำเป็นต้องรองรับเวอร์ชันของ Internet Explorer ก่อนเวอร์ชัน 7

วิธีแก้ปัญหาเดิม (ตอนนี้ล้าสมัย):

สิ่งนี้จะตรวจสอบว่าองค์ประกอบนั้นมองเห็นได้ทั้งหมดในหน้าต่างมุมมองปัจจุบันหรือไม่:

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

คุณสามารถปรับแต่งสิ่งนี้ง่าย ๆ เพื่อกำหนดว่ามีส่วนใดขององค์ประกอบปรากฏอยู่ในมุมมองหน้าต่างหรือไม่:

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

คำตอบ #3

อัปเดต

ในเบราว์เซอร์สมัยใหม่ คุณอาจต้องการตรวจสอบ API ของ Intersection Observer ซึ่งให้ประโยชน์ดังต่อไปนี้:

  • ประสิทธิภาพดีกว่าการฟังเหตุการณ์การเลื่อน
  • ทำงานใน iframe ข้ามโดเมน
  • สามารถบอกได้ว่าธาตุหนึ่งขวางหรือข้ามกับอีกธาตุหนึ่งหรือไม่

Intersection Observer กำลังอยู่ในระหว่างการพัฒนาเพื่อเป็นมาตรฐานเต็มรูปแบบและได้รับการสนับสนุนแล้วใน Chrome 51+, Edge 15+ และ Firefox 55+ และกำลังอยู่ระหว่างการพัฒนาใน Safari นอกจากนี้ยังมี polyfill ให้ใช้งานด้วย


คำตอบก่อนหน้า

มีปัญหาบางประการเกี่ยวกับคำตอบที่ Dan ให้มา ซึ่งอาจทำให้เป็นวิธีแก้ปัญหาที่ไม่เหมาะสมในบางสถานการณ์ ปัญหาบางส่วนเหล่านี้ถูกชี้ให้เห็นในคำตอบของเขาใกล้ส่วนล่าง ว่าโค้ดของเขาจะให้ผลบวกลวงสำหรับองค์ประกอบที่:

  • ถูกซ่อนโดยองค์ประกอบอื่นที่อยู่ด้านหน้าขององค์ประกอบที่กำลังทดสอบ
  • อยู่นอกบริเวณที่มองเห็นได้ขององค์ประกอบแม่หรือลูกหลาน
  • องค์ประกอบหรือองค์ประกอบย่อยของมันที่ถูกซ่อนไว้โดยการใช้คุณสมบัติ CSS clip

ข้อจำกัดเหล่านี้ถูกแสดงให้เห็นในผลลัพธ์ต่อไปนี้ของการทดสอบง่ายๆ:

วิธีแก้ปัญหา: isElementVisible()

นี่คือตัวแก้ปัญหาเหล่านั้น พร้อมผลการทดสอบด้านล่างและคำอธิบายของบางส่วนของโค้ด

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

ผ่านการทดสอบ: http://jsfiddle.net/AndyE/cAY8c/

และผลลัพธ์:

บันทึกเพิ่มเติม

วิธีนี้ก็ไม่ปราศจากข้อจำกัดของมันเอง ตัวอย่างเช่น องค์ประกอบที่ถูกทดสอบโดยมีค่า z-index ต่ำกว่าองค์ประกอบอื่นที่อยู่ในตำแหน่งเดียวกัน จะถูกระบุว่าเป็นการซ่อนอยู่แม้ว่าองค์ประกอบที่อยู่ข้างหน้าไม่ได้ปิดบังส่วนใดของมันจริงๆ อย่างไรก็ตาม วิธีนี้ยังคงมีประโยชน์ในบางกรณีที่วิธีแก้ของแดนไม่ได้ครอบคลุม

ทั้ง element.getBoundingClientRect() และ document.elementFromPoint() เป็นส่วนหนึ่งของข้อกำหนดร่างการทำงานของ CSSOM และได้รับการสนับสนุนอย่างน้อยใน IE 6 ขึ้นไป และ เบราว์เซอร์เดสก์ท็อปส่วนใหญ่ มาเป็นเวลานาน (ถึงแม้จะไม่สมบูรณ์) ดู Quirksmode เกี่ยวกับฟังก์ชันเหล่านี้สำหรับข้อมูลเพิ่มเติม

contains() ถูกใช้เพื่อตรวจสอบว่าองค์ประกอบที่ถูกส่งกลับโดย document.elementFromPoint() เป็นโหนดลูกขององค์ประกอบที่เรากำลังทดสอบการมองเห็นหรือไม่ นอกจากนี้ยังคืนค่า true หากองค์ประกอบที่ถูกส่งกลับเป็นองค์ประกอบเดียวกัน ซึ่งทำให้การตรวจสอบมีความมั่นคงมากขึ้น ฟีเจอร์นี้รองรับในเว็บเบราว์เซอร์หลักทั้งหมด โดย Firefox 9.0 เป็นตัวสุดท้ายที่เพิ่มฟีเจอร์นี้ สำหรับการสนับสนุน Firefox เวอร์ชันเก่าๆ ให้ตรวจสอบประวัติของคำตอบนี้

หากคุณต้องการทดสอบจุดเพิ่มเติมรอบ ๆ องค์ประกอบเพื่อความสามารถในการมองเห็น―เช่น เพื่อให้แน่ใจว่าองค์ประกอบไม่ได้ถูกปกคลุมเกินกว่า 50%―คุณไม่จำเป็นต้องใช้เวลามากในการปรับส่วนสุดท้ายของคำตอบ อย่างไรก็ตาม ควรระวังว่ามันอาจช้ามากถ้าคุณตรวจสอบทุกพิกเซลเพื่อให้แน่ใจว่ามองเห็นได้ 100%

คำตอบ #4

ตอนนี้เรามี API Intersection Observer ของ JavaScript ที่เป็น native ซึ่งเราสามารถตรวจสอบได้ว่าส่วนประกอบอยู่ใน viewport หรือไม่

นี่คือตัวอย่าง

const el = document.querySelector('#el')
const observer = new window.IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {
    console.log('ENTER')
    return
  }
  console.log('LEAVE')
}, {
  root: null,
  threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
})

observer.observe(el);
body {
  height: 300vh;
}

#el {
  margin-top: 100vh;
}
<div id="el">this is element</div>

คำตอบ #5

ฉันลองใช้คำตอบของ Dan แล้ว อย่างไรก็ตาม พีชคณิตที่ใช้ในการกำหนดขอบเขตหมายถึงว่าธาตุต้องมีค่า ≤ ขนาดของ viewport และต้องอยู่ภายใน viewport อย่างสมบูรณ์เพื่อให้ได้ true ซึ่งง่ายต่อการเกิดผลลบเท็จ หากคุณต้องการตรวจสอบว่าธาตุอยู่ใน viewport หรือไม่ คำตอบของ ryanve ใกล้เคียงแล้ว แต่ธาตุที่ทดสอบควรทับกับ viewport ดังนั้นลองสิ่งนี้:

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

คำตอบ #6

ดูแหล่งที่มาของ verge ซึ่งใช้ getBoundingClientRect มันเหมือนกับ:

function inViewport (element) {
  if (!element) return false;
  if (1 !== element.nodeType) return false;

  var html = document.documentElement;
  var rect = element.getBoundingClientRect();

  return !!rect &&
    rect.bottom >= 0 &&
    rect.right >= 0 && 
    rect.left <= html.clientWidth &&
    rect.top <= html.clientHeight;
}

มันจะส่งค่ากลับ true หาก ส่วนใดส่วนหนึ่ง ขององค์ประกอบอยู่ในมุมมองหน้าจอ

คำตอบ #7

ในฐานะบริการสาธารณะ:
คำตอบของแดนพร้อมการคำนวณที่ถูกต้อง (องค์ประกอบสามารถมีขนาดใหญ่กว่าหน้าต่าง โดยเฉพาะบนหน้าจอโทรศัพท์มือถือ) และการทดสอบ jQuery ที่ถูกต้อง รวมถึงการเพิ่ม isElementPartiallyInViewport:

อ้อ ความแตกต่างระหว่าง window.innerWidth กับ document.documentElement.clientWidth คือ clientWidth/clientHeight จะไม่รวมแถบเลื่อน ในขณะที่ 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);
}

กรณีทดสอบ

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

คำตอบ #8

เวอร์ชันที่สั้นกว่าและเร็วกว่าของฉัน:

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

และ jsFiddle ตามที่ต้องการ: https://jsfiddle.net/on1g619L/1/

คำตอบ #9

API ของ Intersection Observer ใหม่ตอบคำถามนี้อย่างตรงไปตรงมามาก

โซลูชันนี้จะต้องมีโพลีฟิลเนื่องจาก Safari, Opera และ Internet Explorer ยังไม่รองรับสิ่งนี้ (โพลีฟิลถูกรวมอยู่ในโซลูชันแล้ว)

ในวิธีแก้ปัญหานี้ จะมีกล่องที่อยู่นอกมุมมองซึ่งเป็นเป้าหมาย (ถูกสังเกต) เมื่อมันเข้ามาในมุมมอง ปุ่มที่อยู่ด้านบนในส่วนหัวจะถูกซ่อน จะปรากฏขึ้นเมื่อกล่องออกจากมุมมอง

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>

คำตอบ #10

วิธีแก้ปัญหาที่ง่ายที่สุด เนื่องจากการสนับสนุนของ Element.getBoundingClientRect() ได้สมบูรณ์แล้ว:

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

คำตอบ #11

ฉันรู้สึกกังวลที่ว่าไม่มีเวอร์ชันที่เน้น jQuery ของฟังก์ชันนั้นให้ใช้งาน เมื่อฉันพบวิธีแก้ไขของแดน ฉันเห็นโอกาสที่จะนำเสนอสิ่งที่เหมาะสำหรับคนที่ชอบเขียนโปรแกรมในสไตล์เชิงวัตถุของ jQuery มันรวดเร็วและราบรื่น และทำงานได้อย่างมหัศจรรย์สำหรับฉัน

บาด้า บิง บาด้า บุ้ม

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

การใช้งาน

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

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

คำตอบ #12

คำตอบทั้งหมดที่ฉันเคยพบที่นี่ตรวจสอบเพียงว่าองค์ประกอบนั้น อยู่ภายใน viewport ปัจจุบันหรือไม่ แต่สิ่งนั้น ไม่ได้หมายความว่ามันจะมองเห็นได้.
แล้วถ้าองค์ประกอบที่กำหนดอยู่ภายใน div ที่มีเนื้อหาเกินขนาด และมันถูกเลื่อนออกจากการมองเห็นล่ะ?

ในการแก้ปัญหานั้น คุณจะต้องตรวจสอบว่าส่วนประกอบนั้นอยู่ภายใต้พ่อแม่ทุกองค์ประกอบหรือไม่
วิธีแก้ของฉันก็ทำแบบนั้นพอดี:

มันยังช่วยให้คุณระบุได้ด้วยว่าต้องให้ส่วนขององค์ประกอบปรากฏให้เห็นเท่าใด

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

วิธีแก้ปัญหานี้มองข้ามความจริงที่ว่าส่วนประกอบอาจมองไม่เห็นเนื่องจากปัจจัยอื่น ๆ เช่น opacity: 0

ฉันได้ทดสอบวิธีแก้ปัญหานี้ใน Chrome และ Internet Explorer 11 แล้ว

คำตอบ #13

ผมพบว่าคำตอบที่ได้รับการยอมรับที่นี่ซับซ้อนเกินไปสำหรับกรณีการใช้งานส่วนใหญ่ โค้ดนี้ทำงานได้ดี (ใช้ jQuery) และแยกความแตกต่างระหว่างองค์ประกอบที่มองเห็นได้ทั้งหมดและบางส่วน:

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

คำตอบ #14

/**
 * Returns Element placement information in Viewport
 * @link https://stackoverflow.com/a/70476497/2453148
 *
 * @typedef {object} ViewportInfo - Whether the element is…
 * @property {boolean} isInViewport - fully or partially in the viewport
 * @property {boolean} isPartiallyInViewport - partially in the viewport
 * @property {boolean} isInsideViewport - fully inside viewport
 * @property {boolean} isAroundViewport - completely covers the viewport
 * @property {boolean} isOnEdge - intersects the edge of viewport
 * @property {boolean} isOnTopEdge - intersects the top edge
 * @property {boolean} isOnRightEdge - intersects the right edge
 * @property {boolean} isOnBottomEdge - is intersects the bottom edge
 * @property {boolean} isOnLeftEdge - is intersects the left edge
 *
 * @param el Element
 * @return {Object} ViewportInfo
 */
function getElementViewportInfo(el) {

    let result = {};

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

    let insideX = rect.left >= 0 && rect.left + rect.width <= windowWidth;
    let insideY = rect.top >= 0 && rect.top + rect.height <= windowHeight;

    result.isInsideViewport = insideX && insideY;

    let aroundX = rect.left < 0 && rect.left + rect.width > windowWidth;
    let aroundY = rect.top < 0 && rect.top + rect.height > windowHeight;

    result.isAroundViewport = aroundX && aroundY;

    let onTop    = rect.top < 0 && rect.top + rect.height > 0;
    let onRight  = rect.left < windowWidth && rect.left + rect.width > windowWidth;
    let onLeft   = rect.left < 0 && rect.left + rect.width > 0;
    let onBottom = rect.top < windowHeight && rect.top + rect.height > windowHeight;

    let onY = insideY || aroundY || onTop || onBottom;
    let onX = insideX || aroundX || onLeft || onRight;

    result.isOnTopEdge    = onTop && onX;
    result.isOnRightEdge  = onRight && onY;
    result.isOnBottomEdge = onBottom && onX;
    result.isOnLeftEdge   = onLeft && onY;

    result.isOnEdge = result.isOnLeftEdge || result.isOnRightEdge ||
        result.isOnTopEdge || result.isOnBottomEdge;

    let isInX =
        insideX || aroundX || result.isOnLeftEdge || result.isOnRightEdge;
    let isInY =
        insideY || aroundY || result.isOnTopEdge || result.isOnBottomEdge;

    result.isInViewport = isInX && isInY;

    result.isPartiallyInViewport =
        result.isInViewport && result.isOnEdge;

    return result;
}

บทความที่เกี่ยวข้อง