Làm cách nào để biết phần tử DOM có thể nhìn thấy trong chế độ xem hiện tại không?


949

Có cách nào hiệu quả để biết liệu phần tử DOM (trong tài liệu HTML) hiện có hiển thị không (xuất hiện trong chế độ xem )?

(Câu hỏi đề cập đến Firefox.)


Phụ thuộc những gì bạn có nghĩa là nhìn thấy. Nếu bạn có nghĩa là nó hiện đang được hiển thị trên trang, với vị trí cuộn, bạn có thể tính toán nó dựa trên các yếu tố y offset và vị trí cuộn hiện tại.
roryf

18
Làm rõ: Hiển thị, như trong hình chữ nhật hiện đang hiển thị. Hiển thị, như không bị ẩn hoặc hiển thị: không? Hiển thị, như không phải đằng sau một cái gì đó khác trong thứ tự Z?
Adam Wright

6
như trong hình chữ nhật hiện đang hiển thị. Cảm ơn. Tái hiện.
benzaita

2
Lưu ý rằng tất cả các câu trả lời ở đây sẽ cung cấp cho bạn kết quả dương tính giả đối với các mục nằm ngoài vùng hiển thị của phần tử cha của chúng (eh với ẩn tràn) nhưng bên trong khu vực chế độ xem.
Andy E

1
Có một triệu câu trả lời và hầu hết đều dài một cách lố bịch. Xem ở đây để có hai lớp lót
leonheess

Câu trả lời:


347

Cập nhật: Diễu hành thời gian và vì vậy có trình duyệt của chúng tôi. Kỹ thuật này không còn được khuyến nghị và bạn nên sử dụng giải pháp của Dan nếu bạn không cần hỗ trợ phiên bản Internet Explorer trước 7.

Giải pháp ban đầu (hiện đã lỗi thời):

Điều này sẽ kiểm tra nếu phần tử hoàn toàn hiển thị trong chế độ xem hiện tại:

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

Bạn có thể sửa đổi điều này một cách đơn giản để xác định xem có phần nào của phần tử hiển thị trong chế độ xem không:

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
Chức năng ban đầu được đăng có một sai lầm. Cần lưu chiều rộng / chiều cao trước khi gán lại ...
Prestaul

15
Điều gì xảy ra nếu phần tử sống trong div có thể cuộn và cuộn ra khỏi chế độ xem ??
amartynov

2
Vui lòng xem lại phiên bản mới hơn của tập lệnh bên dưới
Dan

1
Cũng tò mò về câu hỏi của @ amartynov. Bất cứ ai cũng biết làm thế nào để chỉ đơn giản là nếu một phần tử bị ẩn do tràn phần tử tổ tiên? Tiền thưởng nếu điều này có thể được phát hiện bất kể đứa trẻ được lồng sâu đến mức nào.
Eric Nguyễn

1
@deadManN đệ quy qua DOM nổi tiếng là chậm. Đó là lý do đủ, nhưng các nhà cung cấp trình duyệt cũng đã tạo ra getBoundingClientRectcho mục đích cụ thể là tìm tọa độ phần tử ... Tại sao chúng ta không sử dụng nó?
Prestaul

1402

Bây giờ hầu hết các trình duyệt đều hỗ trợ phương thức getBoundingClientRect , đã trở thành phương thức tốt nhất. Sử dụng một câu trả lời cũ là rất chậm , không chính xáccó một số lỗi .

Các giải pháp được chọn là chính xác là gần như không bao giờ chính xác . Bạn có thể đọc thêm về lỗi của nó.


Giải pháp này đã được thử nghiệm trên Internet Explorer 7 (và sau này), iOS 5 (và sau đó) Safari, Android 2.0 (Eclair) và sau đó, BlackBerry, Opera Mobile và 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() */
    );
}

Cách sử dụng:

Bạn có thể chắc chắn rằng hàm được cung cấp ở trên trả về câu trả lời đúng tại thời điểm nó được gọi, nhưng về khả năng hiển thị của phần tử theo dõi như là một sự kiện thì sao?

Đặt mã sau vào cuối <body>thẻ của bạn :

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

Nếu bạn thực hiện bất kỳ sửa đổi DOM nào, chúng có thể thay đổi khả năng hiển thị của phần tử của bạn.

Nguyên tắc và cạm bẫy phổ biến:

Có lẽ bạn cần theo dõi thu phóng trang / pinch thiết bị di động? jQuery nên xử lý zoom / véo trình duyệt chéo, nếu không đầu tiên hoặc thứ hai liên kết sẽ giúp bạn.

Nếu bạn sửa đổi DOM , nó có thể ảnh hưởng đến khả năng hiển thị của thành phần. Bạn nên kiểm soát điều đó và gọi handler()thủ công. Thật không may, chúng tôi không có bất kỳ onrepaintsự kiện trình duyệt chéo . Mặt khác, cho phép chúng tôi thực hiện tối ưu hóa và chỉ thực hiện kiểm tra lại các sửa đổi DOM có thể thay đổi mức độ hiển thị của một thành phần.

Không bao giờ sử dụng nó trong jQuery $ (tài liệu). Đã () , bởi vì không có CSS ​​bảo hành nào được áp dụng trong thời điểm này. Mã của bạn có thể hoạt động cục bộ với CSS của bạn trên ổ cứng, nhưng một khi được đặt trên máy chủ từ xa thì nó sẽ thất bại.

Sau khi DOMContentLoadedđược kích hoạt, các kiểu được áp dụng , nhưng hình ảnh chưa được tải . Vì vậy, chúng ta nên thêm window.onloadngười nghe sự kiện.

Chúng tôi không thể bắt sự kiện zoom / pinch nào.

Phương sách cuối cùng có thể là mã sau đây:

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

Bạn có thể sử dụng trang tính năng tuyệt vờiVisibiliy của API HTML5 nếu bạn quan tâm nếu tab có trang web của bạn đang hoạt động và hiển thị.

TODO: phương pháp này không xử lý hai tình huống:


6
Tôi đang sử dụng giải pháp này (mặc dù vậy, hãy cẩn thận với lỗi đánh máy "botom"). Ngoài ra còn có một điều cần chú ý, khi yếu tố chúng tôi đang xem xét sẽ có hình ảnh trong đó. Chrome (ít nhất) phải chờ hình ảnh được tải để có giá trị chính xác cho ràng buộc. Có vẻ như Firefox không có "vấn đề" này
Claudio

4
Nó có hoạt động khi bạn đã kích hoạt cuộn trong một thùng chứa bên trong cơ thể. Ví dụ: nó không hoạt động ở đây - agaase.github.io/webpages/demo/isonscreen2.html isEuityInViewport (document.getEuityById ("Internalele")). Internalele có mặt trong một container có kích hoạt cuộn.
agaase

70
Các tính toán cho rằng phần tử nhỏ hơn màn hình. Nếu bạn có các yếu tố cao hoặc rộng, có thể chính xác hơn để sử dụngreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan

11
Mẹo: Đối với những người đang cố gắng thực hiện điều này với jQuery, chỉ cần một lời nhắc nhở thân thiện để truyền vào đối tượng DOM DOM (ví dụ isElementInViewport(document.getElementById('elem')):) chứ không phải đối tượng jQuery (ví dụ isElementInViewport($("#elem)):). Tương đương với jQuery là thêm [0]như vậy : isElementInViewport($("#elem)[0]).
Jiminy

11
el is not defined
M_Willett

176

Cập nhật

Trong các trình duyệt hiện đại, bạn có thể muốn kiểm tra API quan sát giao nhau cung cấp các lợi ích sau:

  • Hiệu suất tốt hơn nghe các sự kiện cuộn
  • Hoạt động trong iframe tên miền
  • Có thể cho biết nếu một phần tử đang cản trở / giao nhau

Intersection Observer đang trên đường trở thành một tiêu chuẩn chính thức và đã được hỗ trợ trong Chrome 51+, Edge 15+ và Firefox 55+ và đang được phát triển cho Safari. Ngoài ra còn có một polyfill có sẵn.


Câu trả lời trước

Có một số vấn đề với câu trả lời do Dan cung cấp có thể khiến nó trở thành một cách tiếp cận không phù hợp cho một số tình huống. Một số trong những vấn đề này được chỉ ra trong câu trả lời của anh ta ở gần cuối, rằng mã của anh ta sẽ đưa ra kết quả dương tính giả cho các yếu tố:

  • Ẩn bởi một yếu tố khác ở phía trước của một yếu tố đang được thử nghiệm
  • Bên ngoài khu vực có thể nhìn thấy của một phần tử cha mẹ hoặc tổ tiên
  • Một yếu tố hoặc con của nó ẩn bằng cách sử dụng CSS clipbất động sản

Những hạn chế này được thể hiện trong các kết quả sau đây của một bài kiểm tra đơn giản :

Kiểm tra thất bại, sử dụng isEuityInViewport

Giải pháp: isElementVisible()

Đây là một giải pháp cho những vấn đề đó, với kết quả thử nghiệm dưới đây và giải thích về một số phần của mã.

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

Kiểm tra vượt qua: http://jsfiddle.net/AndyE/cAY8c/

Và kết quả:

Đã qua kiểm tra, sử dụng isEuityVisible

Ghi chú bổ sung

Phương pháp này không phải là không có giới hạn riêng của nó, tuy nhiên. Chẳng hạn, một phần tử đang được thử nghiệm với chỉ số z thấp hơn so với phần tử khác ở cùng vị trí sẽ được xác định là bị ẩn ngay cả khi phần tử ở phía trước không thực sự che giấu bất kỳ phần nào của nó. Tuy nhiên, phương pháp này có những ứng dụng trong một số trường hợp mà giải pháp của Dan không bao gồm.

Cả hai element.getBoundingClientRect()document.elementFromPoint()là một phần của đặc tả Dự thảo CSSOM và được hỗ trợ trong ít nhất IE 6 trở lên và hầu hết các trình duyệt máy tính để bàn trong một thời gian dài (mặc dù, không hoàn hảo). Xem Quirksmode trên các chức năng này để biết thêm thông tin.

contains()được sử dụng để xem phần tử được trả về có phải document.elementFromPoint()là nút con của phần tử mà chúng tôi đang kiểm tra mức độ hiển thị hay không. Nó cũng trả về true nếu phần tử được trả về là phần tử tương tự. Điều này chỉ làm cho việc kiểm tra mạnh mẽ hơn. Nó được hỗ trợ trong tất cả các trình duyệt chính, Firefox 9.0 là trình duyệt cuối cùng để thêm nó. Để được hỗ trợ Firefox cũ hơn, hãy kiểm tra lịch sử của câu trả lời này.

Nếu bạn muốn kiểm tra nhiều điểm hơn xung quanh thành phần để xem khả năng hiển thị ―, để đảm bảo rằng phần tử không được bao phủ nhiều hơn, giả sử, 50% sẽ không mất nhiều thời gian để điều chỉnh phần cuối của câu trả lời. Tuy nhiên, lưu ý rằng có thể sẽ rất chậm nếu bạn kiểm tra từng pixel để đảm bảo nó hiển thị 100%.


2
Ý của bạn là sử dụng doc.documentEuity.clientWidth? Thay vào đó nên là 'document.documentEuity'? Một lưu ý khác, đây là phương pháp duy nhất cũng hoạt động cho các trường hợp sử dụng như ẩn nội dung của một phần tử để truy cập bằng thuộc tính 'clip' CSS: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Lamping

@klamping: bắt tốt, cảm ơn. Tôi đã sao chép nó ngay ra khỏi mã của mình, nơi tôi đang sử dụng doclàm bí danh document. Vâng, tôi thích nghĩ về điều này như là một giải pháp tốt cho các trường hợp cạnh.
Andy E

2
Đối với tôi nó không hoạt động. Nhưng inViewport () trong câu trả lời trước đang hoạt động trong FF.
Satya Prakash

9
Cũng có thể có ích khi kiểm tra xem phần trung tâm của phần tử có thể nhìn thấy được không nếu bạn có các góc tròn hoặc một biến đổi được áp dụng, vì các góc giới hạn có thể không trả về phần tử mong đợi:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared

2
Không hoạt động trên đầu vào cho tôi (chrome canary 50). Không chắc chắn tại sao, có thể góc tròn bản địa? Tôi đã phải giảm các coords một chút để làm cho nó hoạt động el.contains (efp (orth.left + 1, orth.top + 1)) || el.contains (efp (orth.right-1, orth.top + 1)) || el.contains (efp (orth.right-1, orth.bottom-1)) || el.contains (efp (orth.left + 1, orth.bottom-1))
Rayjax

65

Tôi đã thử câu trả lời của Dan . Tuy nhiên , đại số được sử dụng để xác định giới hạn có nghĩa là phần tử phải có cả size kích thước khung nhìn và hoàn toàn bên trong khung nhìn để có được true, dễ dẫn đến phủ định sai. Nếu bạn muốn xác định xem một phần tử có trong chế độ xem hay không, câu trả lời của ryanve đã gần, nhưng phần tử đang được kiểm tra sẽ chồng lấp lên chế độ xem, vì vậy hãy thử điều này:

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

Là một dịch vụ công cộng:
Câu trả lời của Dan với các tính toán chính xác (yếu tố có thể là> cửa sổ, đặc biệt là trên màn hình điện thoại di động) và kiểm tra jQuery chính xác, cũng như thêm isEuityPartivelyInViewport:

Nhân tiện, sự khác biệt giữa window.innerWidth và document.documentEuity.clientWidth là clientWidth / clientHeight không bao gồm thanh cuộn, trong khi window.innerWidth / height thì không.

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

Trường hợp thử nghiệm

<!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
isElementPartiallyInViewportcũng rất hữu ích Đẹp một.
RJ Cuthbertson

Đối với isEuityInViewport (e): Mã của bạn thậm chí không tải hình ảnh, Điều này là chính xác: function isEuityInViewport (e) {var t = e.getBoundingClientRect (); return t.top> = 0 && t.left> = 0 && t.bottom <= (window.innerHeight || document.documentEuity.clientHeight) && t.right <= (window.innerWidth || document.documentEuity.client }
Arun chauhan

1
@Arun chauhan: Không có mã nào của tôi đang tải hình ảnh, vậy tại sao nó phải như vậy và công thức là chính xác.
Stefan Steiger

1
@targumon: Lý do là sự hỗ trợ của các trình duyệt cũ.
Stefan Steiger

1
@StefanSteiger theo MDN, nó được hỗ trợ từ IE9 nên thực tế an toàn (ít nhất là trong trường hợp của tôi) chỉ sử dụng trực tiếp window.innerHeight. Cảm ơn!
targumon

28

Xem nguồn verge , sử dụng getBoundingClientRect . Nó giống như:

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

}

Nó trả về truenếu bất kỳ phần nào của phần tử nằm trong khung nhìn.


27

Phiên bản ngắn hơn và nhanh hơn của tôi:

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

Và một jsFiddle theo yêu cầu: https://jsfiddle.net/on1g619L/1/


4
Giải pháp của tôi tham lam hơn và nhanh hơn, khi phần tử có bất kỳ pixel nào trong chế độ xem, nó sẽ trả về sai.
Eric Chen

1
Tôi thích nó. Ngắn gọn. Bạn có thể xóa khoảng trắng giữa tên hàm và dấu ngoặc đơn, và giữa dấu ngoặc đơn và dấu ngoặc trên dòng đầu tiên. Không bao giờ thích những không gian đó. Có lẽ đó chỉ là trình soạn thảo Văn bản của tôi mã màu tất cả mà vẫn giúp bạn dễ đọc. function aaa (arg) {statement} Tôi biết rằng điều đó không làm cho nó thực thi nhanh hơn, thay vào đó là giảm thiểu.
YK

21

Tôi thấy điều đáng lo ngại là không có phiên bản chức năng tập trung của jQuery . Khi tôi gặp giải pháp của Dan, tôi đã phát hiện ra cơ hội cung cấp thứ gì đó cho những người thích lập trình theo kiểu OO của jQuery. Nó đẹp và linh hoạt và hoạt động như một cơ duyên đối với tôi.

Bada bing bada bùng nổ

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

Sử dụng

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

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

1
Nẹp xoăn có đủ để đóng câu lệnh if không?
calasyr

1
Tôi không thể làm cho nó hoạt động với nhiều yếu tố của một lớp giống hệt nhau.
rít của Oz

1
@TheWhizofOz tôi đã cập nhật câu trả lời của mình để đưa ra ví dụ về các trường hợp sử dụng có thể khác mà bạn đã đưa ra. may mắn nhất.
r3wt

10

API Intersection Observer mới giải quyết câu hỏi này rất trực tiếp.

Giải pháp này sẽ cần một polyfill vì Safari, Opera và Internet Explorer chưa hỗ trợ điều này (polyfill được bao gồm trong giải pháp).

Trong giải pháp này, có một hộp ngoài tầm nhìn là mục tiêu (được quan sát). Khi nó xuất hiện, nút ở trên cùng trong tiêu đề sẽ bị ẩn. Nó được hiển thị khi hộp rời khỏi chế độ xem.

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
Thực hiện tốt và theo liên kết trong câu trả lời này,sẽ hoạt động trên safari bằng cách thêm <!DOCTYPE html>vào HTML
Alon Eitan

Lưu ý rằng đó IntersectionObserverlà một tính năng thử nghiệm (có thể thay đổi trong tương lai).
Karthik Chintala

2
@KarthikChintala - nó được hỗ trợ trong mọi trình duyệt ngoại trừ IE - và cũng có sẵn một polyfill.
Randy Casburn

Không giải quyết câu hỏi của OP vì chỉ phát hiện các thay đổi : IntersectionObserverchỉ thực hiện gọi lại sau khi di chuyển mục tiêu so với thư mục gốc.
Đồi Brandon

Khi gọi observesự kiện được kích hoạt ngay lập tức cho bạn biết trạng thái giao nhau hiện tại của phần tử được theo dõi. Vì vậy, theo một cách nào đó - nó giải quyết.
Mesqalito

8

Tất cả các câu trả lời tôi gặp ở đây chỉ kiểm tra xem phần tử có được định vị bên trong chế độ xem hiện tại không . Nhưng điều đó không có nghĩa là nó có thể nhìn thấy .
Điều gì xảy ra nếu phần tử đã cho nằm trong một div có nội dung tràn và nó được cuộn ra khỏi tầm nhìn?

Để giải quyết điều đó, bạn phải kiểm tra xem phần tử có được chứa bởi tất cả các bậc cha mẹ hay không.
Giải pháp của tôi thực hiện chính xác điều đó:

Nó cũng cho phép bạn chỉ định bao nhiêu phần tử phải được nhìn thấy.

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

Giải pháp này đã bỏ qua thực tế là các yếu tố có thể không nhìn thấy được do các sự kiện khác, như opacity: 0.

Tôi đã thử nghiệm giải pháp này trong Chrome và Internet Explorer 11.


8

Tôi thấy rằng câu trả lời được chấp nhận ở đây là quá phức tạp đối với hầu hết các trường hợp sử dụng. Mã này thực hiện công việc tốt (sử dụng jQuery) và phân biệt giữa các yếu tố hiển thị đầy đủ và hiển thị một phần:

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

Bạn chắc chắn nên lưu trữ $window = $(window)bên ngoài trình xử lý cuộn.
sam

4

Giải pháp đơn giản nhất là sự hỗ trợ của Element.getBoundingClientRect () đã trở nên hoàn hảo :

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

Làm thế nào điều này hoạt động trên trình duyệt di động? Hầu hết chúng đều có lỗi liên quan đến chế độ xem, với tiêu đề của chúng tăng hoặc giảm khi cuộn và hành vi khác nhau khi bàn phím xuất hiện, tùy thuộc vào đó là android hay ios, v.v.
Kev

@Kev Nên hoạt động tốt tùy thuộc vào thời điểm bạn gọi phương thức này. Nếu bạn gọi nó và sau đó thay đổi kích thước cửa sổ, kết quả rõ ràng có thể không còn đúng nữa. Bạn có thể gọi nó trên mỗi sự kiện thay đổi kích thước tùy thuộc vào loại chức năng bạn muốn. Vui lòng đặt câu hỏi riêng về trường hợp sử dụng cụ thể của bạn và ping tôi tại đây.
leo núi

3

Tôi nghĩ rằng đây là một cách chức năng hơn để làm điều đó. Câu trả lời của Dan không hoạt động trong bối cảnh đệ quy.

Hàm này giải quyết vấn đề khi phần tử của bạn nằm trong các div có thể cuộn khác bằng cách kiểm tra bất kỳ cấp nào theo cách đệ quy cho đến thẻ HTML và dừng ở sai đầu tiên.

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

Các câu trả lời được chấp nhận nhiều nhất không hoạt động khi phóng to Google Chrome trên Android. Kết hợp với câu trả lời của Dan , để tính đến Chrome trên Android, phải sử dụng visualViewport . Ví dụ sau chỉ tính đến kiểm tra dọc và sử dụng jQuery cho chiều cao cửa sổ:

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

Đây là giải pháp của tôi. Nó sẽ hoạt động nếu một phần tử được ẩn bên trong một thùng chứa có thể cuộn được.

Đây là bản demo (thử kích thước lại cửa sổ)

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

Tôi chỉ cần kiểm tra xem nó có hiển thị trong trục Y hay không (đối với tính năng tải thêm bản ghi Ajax).


2

Dựa trên giải pháp của dan , tôi đã tiến hành dọn dẹp việc triển khai để việc sử dụng nó nhiều lần trên cùng một trang sẽ dễ dàng hơn:

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

});

Cách tôi đang sử dụng là khi phần tử cuộn vào chế độ xem, tôi đang thêm một lớp kích hoạt hoạt hình khung hình chính CSS. Điều này khá đơn giản và đặc biệt hiệu quả khi bạn có hơn 10 điều cần thiết để hoạt hình một cách có điều kiện trên một trang.


Bạn chắc chắn nên lưu trữ $window = $(window)bên ngoài trình xử lý cuộn
Adam Rehal

2

Hầu hết các cách sử dụng trong các câu trả lời trước đều thất bại ở những điểm sau:

-Khi bất kỳ pixel nào của một phần tử được hiển thị, nhưng không phải là " một góc ",

-Khi một phần tử lớn hơn khung nhìn và chính giữa ,

- Hầu hết trong số họ chỉ kiểm tra một yếu tố đơn lẻ trong tài liệu hoặc cửa sổ .

Chà, đối với tất cả những vấn đề này, tôi có một giải pháp và các mặt tích cực là:

-Bạn có thể quay lại visiblekhi chỉ một pixel từ bất kỳ phía nào xuất hiện và không phải là một góc,

-Bạn vẫn có thể quay lại visibletrong khi phần tử lớn hơn chế độ xem,

-Bạn có thể chọn parent elementhoặc bạn có thể tự động để nó chọn,

-Works về các yếu tố được thêm động .

Nếu bạn kiểm tra các đoạn bên dưới, bạn sẽ thấy sự khác biệt trong việc sử dụng overflow-scrolltrong bộ chứa của phần tử sẽ không gây ra bất kỳ rắc rối nào và thấy rằng không giống như các câu trả lời khác ở đây ngay cả khi một pixel xuất hiện từ bất kỳ phía nào hoặc khi một phần tử lớn hơn chế độ xem và chúng ta đang nhìn thấy bên trong pixel của phần tử nó vẫn hoạt động.

Cách sử dụng rất đơn giản:

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

Một bản trình diễn mà không có giá trị crossSearchAlgorithmnào hữu ích cho các phần tử lớn hơn chế độ xem kiểm tra phần tử 3 pixel bên trong để xem:

Bạn thấy, khi bạn ở trong phần tử3, nó không thể biết được nó có nhìn thấy hay không, bởi vì chúng tôi chỉ kiểm tra xem phần tử có thể nhìn thấy từ các cạnh hay góc không .

Và cái này bao gồm crossSearchAlgorithmcho phép bạn vẫn quay lại visiblekhi phần tử lớn hơn khung nhìn:

Chơi trò chơi với: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Mã này được tạo để có thông tin chính xác hơn nếu bất kỳ phần nào của phần tử được hiển thị trong dạng xem hay không. Đối với các tùy chọn hiệu suất hoặc chỉ các slide dọc, không sử dụng điều này! Mã này có hiệu quả hơn trong các trường hợp vẽ.


1

Một giải pháp tốt hơn:

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
Bạn nên thử và giải thích tại sao phiên bản của bạn tốt hơn. Khi nó đứng, nó trông ít nhiều giống như các giải pháp khác.
Andy E

1
Giải pháp tuyệt vời nó có BOX / ScrollContainer và không sử dụng WINDOW (chỉ khi nó không được chỉ định). Hãy xem mã hơn là một giải pháp phổ quát hơn (Đã tìm kiếm nó rất nhiều)
teter

1

Đơn giản như nó có thể nhận được, IMO:

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

0

Đây là một hàm cho biết nếu một phần tử hiển thị trong chế độ xem hiện tại của phần tử cha :

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

Điều này kiểm tra nếu một phần tử ít nhất là một phần trong chế độ xem (kích thước dọc):

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

Tôi đã có cùng một câu hỏi và tìm ra nó bằng cách sử dụng getBoundingClientRect ().

Mã này hoàn toàn 'chung chung' và chỉ phải viết một lần để nó hoạt động (bạn không phải viết nó ra cho mỗi yếu tố mà bạn muốn biết là trong chế độ xem).

Mã này chỉ kiểm tra xem nó có theo chiều dọc trong chế độ xem không , theo chiều ngang . Trong trường hợp này, các phần tử (mảng) 'biến' chứa tất cả các phần tử mà bạn đang kiểm tra theo chiều dọc trong chế độ xem, do đó, lấy bất kỳ phần tử nào bạn muốn ở bất cứ đâu và lưu trữ chúng ở đó.

'Vòng lặp', lặp qua từng phần tử và kiểm tra xem nó có theo chiều dọc trong chế độ xem không. Mã này thực thi mỗi khi người dùng cuộn! Nếu đỉnh getBoudingClientRect (). Nhỏ hơn 3/4 chế độ xem (phần tử là một phần tư trong chế độ xem), thì nó đăng ký là 'trong chế độ xem'.

Vì mã là chung chung, bạn sẽ muốn biết phần tử 'which' nào trong chế độ xem. Để tìm ra điều đó, bạn có thể xác định nó theo thuộc tính tùy chỉnh, tên nút, id, tên lớp và hơn thế nữa.

Đây là mã của tôi (cho tôi biết nếu nó không hoạt động; nó đã được thử nghiệm trong Internet Explorer 11, Firefox 40.0.3, Phiên bản Chrome 45.0.2454,85 ​​m, Opera 31.0.1889.174 và Edge với Windows 10, [chưa có 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

Đây là giải pháp dễ dàng và nhỏ đã làm việc cho tôi.

Ví dụ : Bạn muốn xem phần tử có hiển thị trong phần tử cha có cuộn tràn không.

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

Tất cả các câu trả lời ở đây đang xác định xem phần tử có được chứa đầy đủ trong chế độ xem không, chỉ hiển thị theo một cách nào đó. Ví dụ: nếu chỉ một nửa hình ảnh hiển thị ở dưới cùng của chế độ xem, các giải pháp ở đây sẽ thất bại, xem xét rằng "bên ngoài".

Tôi đã có một trường hợp sử dụng khi tôi lười tải qua IntersectionObserver, nhưng do hình ảnh động xảy ra trong cửa sổ bật lên, tôi không muốn quan sát bất kỳ hình ảnh nào đã được giao nhau khi tải trang. Để làm điều đó, tôi đã sử dụng mã sau đây:

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

Điều này về cơ bản là kiểm tra xem liệu giới hạn trên hay dưới được độc lập trong chế độ xem. Đầu đối diện có thể ở bên ngoài, nhưng miễn là một đầu ở trong, nó "hiển thị" ít nhất một phần.


-1

Tôi sử dụng chức năng này (nó chỉ kiểm tra xem y có ở trong màn hình hay không vì hầu hết thời gian x không cần thiết)

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

Đối với một thử thách tương tự, tôi thực sự rất thích ý chính này trong đó hiển thị một polyfill cho scrollIntoView IfNeeded () .

Tất cả các Kung Fu cần thiết để trả lời đều nằm trong khối này:

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;

thisđề cập đến yếu tố mà bạn muốn biết nếu đó là, ví dụ, overTophoặc overBottom- bạn chỉ nên nhận được sự trôi dạt ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.