簡體   English   中英

如何判斷 DOM 元素在當前視口中是否可見?

[英]How can I tell if a DOM element is visible in the current viewport?

有沒有一種有效的方法來判斷 DOM 元素(在 HTML 文檔中)當前是否可見(出現在viewport中)?

(問題參考Firefox。)

現在大多數瀏覽器都支持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);

如果您關心您的網頁的選項卡是否處於活動狀態且可見,您可以使用 HTML5 API 的超棒功能pageVisibiliy

TODO:此方法不處理兩種情況:

更新:時間在進步,我們的瀏覽器也在進步。 不再推薦這種技術,如果您不需要支持 7 之前的 Internet Explorer 版本,您應該使用Dan 的解決方案

原始解決方案(現已過時):

這將檢查元素是否在當前視口中完全可見:

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

更新

在現代瀏覽器中,您可能需要查看Intersection Observer API ,它提供以下好處:

  • 比監聽滾動事件更好的性能
  • 適用於跨域 iframe
  • 可以判斷一個元素是否阻礙/與另一個元素相交

Intersection Observer 正在成為一個成熟的標准,並且已經在 Chrome 51+、Edge 15+ 和 Firefox 55+ 中得到支持,並且正在為 Safari 開發。 還有一個polyfill可用。


上一個答案

Dan 提供的答案存在一些問題,這可能使其不適用於某些情況。 他在底部附近的回答中指出了其中一些問題,他的代碼將對以下元素給出誤報:

  • 被正在測試的元素前面的另一個元素隱藏
  • 在父元素或祖先元素的可見區域之外
  • 使用 CSS clip屬性隱藏的元素或其子元素

這些限制在以下簡單測試的結果中得到了證明:

測試失敗,使用 isElementInViewport

解決方案: 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/

結果:

通過測試,使用 isElementVisible

補充說明

然而,這種方法並非沒有其自身的局限性。 例如,一個被測試的元素的 z-index 低於同一位置的另一個元素,即使前面的元素實際上並沒有隱藏它的任何部分,它也會被標識為隱藏。 盡管如此,這種方法在 Dan 的解決方案未涵蓋的某些情況下仍有其用途。

element.getBoundingClientRect()document.elementFromPoint()都是 CSSOM 工作草案規范的一部分,並且至少在 IE 6 及更高版本和大多數桌面瀏覽器中得到了很長時間的支持(盡管並不完美)。 有關更多信息,請參閱有關這些功能的 Quirksmode

contains()用於查看document.elementFromPoint()返回的元素是否是我們正在測試可見性的元素的子節點。 如果返回的元素是相同的元素,它也會返回 true。 這只會使檢查更加健壯。 所有主流瀏覽器都支持它,Firefox 9.0 是最后一個添加它的瀏覽器。 對於較舊的 Firefox 支持,請查看此答案的歷史記錄。

如果您想測試元素周圍更多點的可見性——即確保元素不被超過 50% 覆蓋——調整答案的最后部分並不需要太多。 但是,請注意,如果您檢查每個像素以確保其 100% 可見,它可能會非常慢。

我嘗試了Dan 的回答,但是,用於確定邊界的代數意味着元素必須既 ≤ 視口大小並且完全在視口內才能得到true ,很容易導致誤報。 如果你想確定一個元素是否在視口中, ryanve 的答案很接近,但被測試的元素應該與視口重疊,所以試試這個:

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

請參閱使用getBoundingClientRectverge的來源。 就像是:

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

作為公共服務:
丹的答案正確計算(元素可以是>窗口,尤其是在手機屏幕上),正確的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>

我們現在有一個原生的 javascript Intersection Observer API ,我們可以從中檢測元素是否在視口中。

這是示例

 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>

我更短更快的版本:

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/

我發現沒有可用的功能的以jQuery為中心的版本令人不安。 當我遇到Dan 的解決方案時,我發現有機會為喜歡以 jQuery OO 風格編程的人們提供一些東西。 它既漂亮又活潑,對我來說就像一種魅力。

巴達兵巴達熱潮

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

新的Intersection Observer API非常直接地解決了這個問題。

這個解決方案需要一個 polyfill,因為 Safari、Opera 和 Internet Explorer 還不支持這個(polyfill 包含在解決方案中)。

在這個解決方案中,有一個看不見的盒子是目標(觀察到的)。 當它進入視圖時,標題頂部的按鈕是隱藏的。 一旦盒子離開視圖,它就會顯示出來。

 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>

有一個叫做inview的 jQuery插件可以完成這項工作

作為支持Element.getBoundingClientRect()的最簡單的解決方案已經變得完美了:

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

我在這里遇到的所有答案只檢查元素是否位於當前視口內 但這並不意味着它是可見的
如果給定元素位於內容溢出的 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 中測試了這個解決方案。

我發現對於大多數用例來說,這里接受的答案過於復雜。 這段代碼很好地完成了這項工作(使用 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");
    }
});

我認為這是一種更實用的方法。 丹的答案在遞歸上下文中不起作用。

當您的元素在其他可滾動的 div 中時,此函數通過遞歸測試任何級別直到 HTML 標記來解決問題,並在第一個 false 處停止。

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

這是我的解決方案。 如果元素隱藏在可滾動容器中,它將起作用。

這是一個演示(嘗試重新調整窗口大小)

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

我只需要檢查它是否在 Y 軸上可見(對於滾動 Ajax 加載更多記錄功能)。

在 Android 上放大 Google Chrome 時,最接受的答案不起作用。 結合Dan 的回答,為了說明 Android 上的 Chrome,必須使用visualViewport 以下示例僅考慮垂直檢查並使用 jQuery 作為窗口高度:

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

基於dan 的解決方案,我嘗試清理實現,以便在同一頁面上多次使用它更容易:

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

});

我使用它的方式是,當元素滾動到視圖中時,我添加了一個觸發 CSS 關鍵幀動畫的類。 當您在頁面上有 10 多個有條件的動畫時,它非常簡單,並且效果特別好。

以前答案中的大多數用法在這些方面都失敗了:

-當元素的任何像素可見,但不是“角落”時,

- 當一個元素大於視口並且居中時,

- 他們中的大多數只檢查文檔或窗口中的單個元素。

好吧,對於所有這些問題,我都有一個解決方案,而且好處是:

- 當任何一側僅顯示一個像素並且不是角落時,您可以返回visible

- 當元素大於視口時,您仍然可以返回visible

- 你可以選擇你的parent element ,也可以自動讓它選擇,

- 也適用於動態添加的元素

如果您檢查下面的代碼片段,您將看到在元素容器中使用overflow-scroll的區別不會造成任何麻煩,並且看到與此處的其他答案不同,即使像素從任何一側顯示或當元素大於視口時,我們正在看到它仍然有效的元素的內部像素

用法很簡單:

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

沒有crossSearchAlgorithm的演示,這對於大於視口檢查 element3 內部像素的元素很有用:

 function isVisible(element, parent, crossSearchAlgorithm) { var rect = element.getBoundingClientRect(), prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(), csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false, efp = function (x, y) { return document.elementFromPoint(x, y) }; // Return false if it's not in the viewport if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) { return false; } var flag = false; // Return true if left to right any border pixel reached for (var x = rect.left; x < rect.right; x++) { if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) { flag = true; break; } } // Return true if top to bottom any border pixel reached if (flag == false) { for (var y = rect.top; y < rect.bottom; y++) { if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) { flag = true; break; } } } if(csa) { // Another algorithm to check if element is centered and bigger than viewport if (flag == false) { var x = rect.left; var y = rect.top; // From top left to bottom right while(x < rect.right || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true; break; } if(x < rect.right) { x++; } if(y < rect.bottom) { y++; } } if (flag == false) { x = rect.right; y = rect.top; // From top right to bottom left while(x > rect.left || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true; break; } if(x > rect.left) { x--; } if(y < rect.bottom) { y++; } } } } } return flag; } // Check multiple elements visibility document.getElementById('container').addEventListener("scroll", function() { var elementList = document.getElementsByClassName("element"); var console = document.getElementById('console'); for (var i=0; i < elementList.length; i++) { // I did not define parent, so it will be element's parent if (isVisible(elementList[i])) { console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!"; break; } else { console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!"; } } }); // Dynamically added elements for(var i=4; i <= 6; i++) { var newElement = document.createElement("div"); newElement.id = "element" + i; newElement.classList.add("element"); document.getElementById('container').appendChild(newElement); }
 #console { background-color: yellow; } #container { width: 300px; height: 100px; background-color: lightblue; overflow-y: auto; padding-top: 150px; margin: 45px; } .element { margin: 400px; width: 400px; height: 320px; background-color: green; } #element3 { position: relative; margin: 40px; width: 720px; height: 520px; background-color: green; } #element3::before { content: ""; position: absolute; top: -10px; left: -10px; margin: 0px; width: 740px; height: 540px; border: 5px dotted green; background: transparent; }
 <div id="console"></div> <div id="container"> <div id="element1" class="element"></div> <div id="element2" class="element"></div> <div id="element3" class="element"></div> </div>

你看,當你在 element3 里面時,它無法判斷它是否可見,因為我們只檢查元素是否從可見。

而這個包括crossSearchAlgorithm ,它允許您在元素大於視口時仍然返回visible

 function isVisible(element, parent, crossSearchAlgorithm) { var rect = element.getBoundingClientRect(), prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(), csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false, efp = function (x, y) { return document.elementFromPoint(x, y) }; // Return false if it's not in the viewport if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) { return false; } var flag = false; // Return true if left to right any border pixel reached for (var x = rect.left; x < rect.right; x++) { if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) { flag = true; break; } } // Return true if top to bottom any border pixel reached if (flag == false) { for (var y = rect.top; y < rect.bottom; y++) { if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) { flag = true; break; } } } if(csa) { // Another algorithm to check if element is centered and bigger than viewport if (flag == false) { var x = rect.left; var y = rect.top; // From top left to bottom right while(x < rect.right || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true; break; } if(x < rect.right) { x++; } if(y < rect.bottom) { y++; } } if (flag == false) { x = rect.right; y = rect.top; // From top right to bottom left while(x > rect.left || y < rect.bottom) { if (element.contains(efp(x,y))) { flag = true; break; } if(x > rect.left) { x--; } if(y < rect.bottom) { y++; } } } } } return flag; } // Check multiple elements visibility document.getElementById('container').addEventListener("scroll", function() { var elementList = document.getElementsByClassName("element"); var console = document.getElementById('console'); for (var i=0; i < elementList.length; i++) { // I did not define parent so it will be element's parent // and it will do crossSearchAlgorithm if (isVisible(elementList[i],null,true)) { console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!"; break; } else { console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!"; } } }); // Dynamically added elements for(var i=4; i <= 6; i++) { var newElement = document.createElement("div"); newElement.id = "element" + i; newElement.classList.add("element"); document.getElementById('container').appendChild(newElement); }
 #console { background-color: yellow; } #container { width: 300px; height: 100px; background-color: lightblue; overflow-y: auto; padding-top: 150px; margin: 45px; } .element { margin: 400px; width: 400px; height: 320px; background-color: green; } #element3 { position: relative; margin: 40px; width: 720px; height: 520px; background-color: green; } #element3::before { content: ""; position: absolute; top: -10px; left: -10px; margin: 0px; width: 740px; height: 540px; border: 5px dotted green; background: transparent; }
 <div id="console"></div> <div id="container"> <div id="element1" class="element"></div> <div id="element2" class="element"></div> <div id="element3" class="element"></div> </div>

JSFiddle 玩:http: //jsfiddle.net/BerkerYuceer/grk5az2c/

如果元素的任何部分是否顯示在視圖中,則此代碼用於提供更精確的信息。 對於性能選項或僅垂直幻燈片,請勿使用此選項! 此代碼在繪制案例時更有效。

更好的解決方案:

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

我有同樣的問題,並通過使用 getBoundingClientRect() 解決了這個問題。

這段代碼是完全“通用的”,只需編寫一次就可以工作(您不必為您想知道的每個元素都寫出它在視口中)。

此代碼僅檢查它是否在視口中垂直,而不是水平 在這種情況下,變量(數組)'elements' 包含您正在檢查的所有元素,以便在視口中垂直放置,因此在任何地方抓取您想要的任何元素並將它們存儲在那里。

“for循環”循環遍歷每個元素並檢查它是否在視口中垂直。 每次用戶滾動時都會執行此代碼! 如果 getBoudingClientRect().top 小於視口的 3/4(元素在視口中的四分之一),它會注冊為“在視口中”。

由於代碼是通用的,您將想知道“哪個”元素在視口中。 要找出它,您可以通過自定義屬性、節點名稱、id、類名稱等來確定它。

這是我的代碼(告訴我它是否不起作用;它已經在 Internet Explorer 11、Firefox 40.0.3、Chrome 版本 45.0.2454.85 m、Opera 31.0.1889.174 和 Windows 10 的 Edge 中進行了測試,[還沒有 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.');
   }
};

這是一個函數,它告訴元素是否在元素的當前視口中可見:

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

盡可能簡單,IMO:

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

這將檢查元素是否至少部分在視圖中(垂直尺寸):

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

這是對我有用的簡單而小型的解決方案。

示例:您想查看該元素是否在具有溢出滾動的父元素中可見。

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

這里的所有答案都是確定元素是否完全包含在視口中,而不僅僅是以某種方式可見。 例如,如果在視圖底部只能看到一半圖像,則考慮到“外部”,此處的解決方案將失敗。

我有一個用例,我通過IntersectionObserver進行延遲加載,但由於在彈出期間發生動畫,我不想觀察在頁面加載時已經相交的任何圖像。 為此,我使用了以下代碼:

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

這基本上是檢查頂部或底部邊界是否在視口中獨立。 另一端可能在外面,但只要一端在里面,它至少部分是“可見的”。

視口信息

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

我使用這個函數(它只檢查 y 是否在屏幕內,因為大多數時候不需要 x)

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

}

這是一個檢查給定元素是否在其父元素中完全可見的片段:

export const visibleInParentViewport = (el) => {
  const elementRect = el.getBoundingClientRect();
  const parentRect = el.parentNode.getBoundingClientRect();

  return (
    elementRect.top >= parentRect.top &&
    elementRect.right >= parentRect.left &&
    elementRect.top + elementRect.height <= parentRect.bottom &&
    elementRect.left + elementRect.width <= parentRect.right
  );
}
 const isHTMLElementInView = (element: HTMLElement) => {
  const rect = element?.getBoundingClientRect()

  if (!rect) return
  return rect.top <= window.innerHeight && rect.bottom >= 0
 }

這個 function 檢查元素是否在垂直水平的視口中。

Domysee 的回答https://stackoverflow.com/a/37998526接近正確。

許多示例使用“完全包含在視口中”,他的代碼使用百分比來允許部分可見。 他的代碼還解決了大多數示例忽略的“是父剪輯視圖”的問題。

一個缺失的元素是父級滾動條的影響getBoundingClientRect返回父級的外部矩形,其中包括滾動條,而不是內部矩形,后者不包含。 子級可以隱藏在父級滾動條后面,並且在不可見時被視為可見。

推薦的觀察者模式不適合我的用例:使用箭頭鍵更改表中當前選定的行,並確保新選擇可見。 為此使用觀察者將過於復雜。

這是一些代碼 -

它包括一個額外的 hack ( fudgeY ),因為我的表有一個無法通過直接方式檢測到的粘性標題(並且自動處理它會非常乏味)。 此外,對於所需的可見分數,它使用小數(0 到 1)而不是百分比。 (對於我的情況,我需要完整的 y,而 x 不相關)。

function intersectRect(r1, r2) {
    var r = {};
    r.left = r1.left < r2.left ? r2.left : r1.left;
    r.top = r1.top < r2.top ? r2.top : r1.top;
    r.right = r1.right < r2.right ? r1.right : r2.right;
    r.bottom = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
    if (r.left < r.right && r.top < r.bottom)
        return r;
    return null;
}

function innerRect(e) {
    var b,r;
    b = e.getBoundingClientRect();
    r = {};
    r.left = b.left;
    r.top = b.top;
    r.right = b.left + e.clientWidth;
    r.bottom = b.top + e.clientHeight;
    return r;
}

function isViewable(e, fracX, fracY, fudgeY) {
    // ref https://stackoverflow.com/a/37998526
    // intersect all the rects and then check the result once
    // innerRect: mind the scroll bars
    // fudgeY: handle "sticky" thead in parent table.  Ugh.
    var r, pr, er;

    er = e.getBoundingClientRect();
    r = er;
    for (;;) {
        e = e.parentElement;
        if (!e)
            break;
        pr = innerRect(e);
        if (fudgeY)
            pr.top += fudgeY;
        r = intersectRect(r, pr);
        if (!r)
            return false;
    }

    if (fracX && ((r.right-r.left) / (er.right-er.left)) < (fracX-0.001))
        return false;
    if (fracY && ((r.bottom-r.top) / (er.bottom-er.top)) < (fracY-0.001))
        return false;
    return true;
}

對於類似的挑戰,我真的很喜歡這個scrollIntoViewIfNeeded()公開 polyfill 的要點。

回答所需的所有必要功夫都在此塊內:

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指的是您想知道的元素,例如, overTop還是overBottom - 您應該得到漂移...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM