是否有一种有效的方法来判断DOM元素(在HTML文档中)当前是否可见(出现在视口中)?

(这个问题指的是Firefox。)


当前回答

我有同样的问题,并通过使用getBoundingClientRect()来解决。

这段代码完全是“通用的”,只需要写一次就可以工作(你不需要为你想知道的每个元素都写出来)。

这段代码只检查它在视口中是否垂直,而不是水平。在本例中,变量(array)'elements'保存了所有你要检查的垂直在视口中的元素,所以在任何地方抓取任何你想要的元素并将它们存储在那里。

for循环遍历每个元素并检查它是否垂直地位于视口中。这段代码在用户每次滚动时执行!如果getBoudingClientRect()。Top小于viewport的3/4(元素在viewport中的四分之一),它注册为“在viewport中”。

因为代码是通用的,你会想知道“哪个”元素在视口中。要找出这一点,可以通过自定义属性、节点名、id、类名等确定。

这是我的代码(如果它不起作用,请告诉我;它已在Internet Explorer 11、Firefox 40.0.3、Chrome Version 45.0.2454.85 m、Opera 31.0.1889.174和Edge with Windows 10(还没有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.');
   }
};

其他回答

我认为这是一种更实用的方法。 Dan的答案在递归上下文中不起作用。

此函数通过递归测试HTML标记之前的任何级别,并在第一个false处停止,解决了当您的元素位于其他可滚动div中的问题。

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

在我看来,非常简单:

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

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

作为公共服务: Dan的答案与正确的计算(元素可以>窗口,特别是在手机屏幕上),并正确的jQuery测试,以及添加isElementPartiallyInViewport:

顺便说一下,窗户和窗户的区别。innerWidth和document.documentElement.clientWidth是clientWidth/clientHeight不包括滚动条,while窗口。innerWidth /高。

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>
 const isHTMLElementInView = (element: HTMLElement) => {
  const rect = element?.getBoundingClientRect()

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

这个函数检查元素是否在垂直水平的视口中。