【问题标题】:How can I tell if a DOM element is visible in the current viewport?如何判断 DOM 元素在当前视口中是否可见?
【发布时间】:2010-09-12 13:06:43
【问题描述】:

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

(问题指的是 Firefox。)

【问题讨论】:

  • 取决于可见的含义。如果您的意思是它当前是否显示在页面上,给定滚动位置,您可以根据元素 y 偏移量和当前滚动位置来计算它。
  • 我添加了my own solution 来解决这个问题
  • 这些解决方案是否考虑了 dom 节点的 z-index 以及可能通过隐藏 z-index 较低的元素来特别影响可见性?
  • 所提供的答案均不适用于生成的绝对定位元素。
  • 一百万个答案,而且大多数都长得离谱。 See here for a two-liner

标签: javascript html firefox dom browser


【解决方案1】:

更新:时间在进步,我们的浏览器也在进步。 不再推荐使用此技术,如果您不需要支持 7 之前的 Internet Explorer 版本,则应使用 Dan's solution

原始解决方案(现已过时):

这将检查元素是否在当前视口中完全可见:

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

【讨论】:

  • 原贴的功能有误。重新分配el之前需要保存宽度/高度...
  • 如果元素位于可滚动的 div 中并滚动到视图之外怎么办??
  • 请查看以下脚本的较新版本
  • 也很好奇@amartynov 的问题。任何人都知道如何简单地判断一个元素是否由于祖先元素的溢出而被隐藏?如果无论孩子的嵌套有多深,都可以检测到这一点,则奖励。
  • @deadManN 在 DOM 中递归是出了名的慢。理由很充分,但是浏览器供应商还创建了getBoundingClientRect,专门用于查找元素坐标……我们为什么不使用它?
【解决方案2】:

现在most browsers 支持getBoundingClientRect 方法,这已成为最佳实践。使用旧答案非常慢,not accuratehas several bugs

选择正确的解决方案是almost never precise


此解决方案已在 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() */
    );
}

使用方法:

您可以确定上面给出的函数在调用它的那一刻返回正确的答案,但是跟踪元素作为事件的可见性呢?

将以下代码放在&lt;body&gt; 标签的底部:

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 应该处理 zoom/pinch 跨浏览器,否则 firstsecond 链接应该可以帮助您。

如果您修改 DOM,它会影响元素的可见性。您应该控制它并手动调用handler()。不幸的是,我们没有任何跨浏览器onrepaint 事件。另一方面,这允许我们进行优化并仅对可能改变元素可见性的 DOM 修改执行重新检查。

永远不会仅在 jQuery $(document).ready() 中使用它,因为此时 there is no warranty CSS has been applied。您的代码可以在本地与硬盘上的 CSS 一起工作,但一旦放到远程服务器上,它就会失败。

DOMContentLoaded 被解雇后,styles are applied,但the images are not loaded yet。所以,我们应该添加window.onload事件监听器。

我们还不能捕捉缩放/捏合事件。

最后的手段可能是以下代码:

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

如果您关心您的网页的选项卡是否处于活动状态且可见,您可以使用 HTML5 API 的出色功能 pageVisibiliy

TODO:此方法不处理两种情况:

【讨论】:

  • 我正在使用这个解决方案(不过要注意“底部”错字)。当我们正在考虑的元素中包含图像时,还有一些事情需要注意。 Chrome (至少)必须等待加载的图像具有 boundingRectangle 的确切值。似乎Firefox没有这个“问题”
  • 当您在主体内的容器中启用滚动时它是否工作。例如,它在这里不起作用 - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport(document.getElementById("innerele"))。 innerele 存在于启用滚动的容器内。
  • 计算假设元素小于屏幕。如果您有高或宽的元素,使用return (rect.bottom &gt;= 0 &amp;&amp; rect.right &gt;= 0 &amp;&amp; rect.top &lt;= (window.innerHeight || document.documentElement.clientHeight) &amp;&amp; rect.left &lt;= (window.innerWidth || document.documentElement.clientWidth)); 可能更准确
  • 提示:对于那些试图用 jQuery 实现这一点的人,只是一个友好的提醒,传递 HTML DOM 对象(例如,isElementInViewport(document.getElementById('elem')))而不是 jQuery 对象(例如,isElementInViewport($("#elem)))。 jQuery 等价物是像这样添加[0]isElementInViewport($("#elem)[0])
  • el is not defined
【解决方案3】:

查看verge 的来源,它使用getBoundingClientRect。就像:

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

}

如果元素的任何部分在视口中,则返回true

【讨论】:

    【解决方案4】:

    更好的解决方案:

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

    【讨论】:

    • 您应该尝试解释为什么您的版本更好。就目前而言,它看起来或多或少与其他解决方案相同。
    • 很好的解决方案,它有一个 BOX/ScrollContainer 并且不使用 WINDOW(仅在未指定的情况下)。看看代码而不是评价它是一个更通用的解决方案(正在搜索它很多)
    【解决方案5】:

    更新

    在现代浏览器中,您可能需要查看Intersection Observer API,它提供以下好处:

    • 比监听滚动事件更好的性能
    • 在跨域 iframe 中工作
    • 可以判断一个元素是否阻碍/与另一个元素相交

    Intersection Observer 正在成为一个成熟的标准,已经在 Chrome 51+、Edge 15+ 和 Firefox 55+ 中得到支持,并且正在为 Safari 开发。还有一个polyfill 可用。


    上一个答案

    answer provided by Dan 存在一些问题,这可能使其不适用于某些情况。他在底部附近的回答中指出了其中一些问题,他的代码会对以下元素给出误报:

    • 被正在测试的元素前面的另一个元素隐藏
    • 在父元素或祖先元素的可见区域之外
    • 使用 CSS clip 属性隐藏的元素或其子元素

    simple test 的以下结果证明了这些限制:

    解决办法: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 低于同一位置的另一个元素,即使前面的元素实际上并没有隐藏它的任何部分,它也会被标识为隐藏。不过,这种方法在 Dan 的解决方案没有涵盖的某些情况下仍有用处。

    element.getBoundingClientRect()document.elementFromPoint() 都是 CSSOM 工作草案规范的一部分,并且至少在 IE 6 及更高版本以及大多数桌面浏览器中得到了很长时间的支持(尽管并不完美) .请参阅Quirksmode on these functions 了解更多信息。

    contains() 用于查看document.elementFromPoint() 返回的元素是否是我们正在测试可见性的元素的子节点。如果返回的元素是相同的元素,它也会返回 true。这只会使检查更加健壮。所有主流浏览器都支持它,Firefox 9.0 是最后一个添加它的浏览器。对于较旧的 Firefox 支持,请查看此答案的历史记录。

    如果您想测试元素周围更多点的可见性——即确保元素不被超过 50% 覆盖——调整答案的最后部分并不需要太多.但是请注意,如果您检查每个像素以确保其 100% 可见,它可能会非常慢。

    【讨论】:

    • 您的意思是使用 doc.documentElement.clientWidth 吗?那应该是'document.documentElement'吗?另一方面,这是唯一适用于用例的方法,例如使用 CSS 'clip' 属性隐藏元素的内容以实现可访问性:snook.ca/archives/html_and_css/hiding-content-for-accessibility
    • @klamping:很好,谢谢。我将它从我使用doc 作为document 的别名的代码中直接复制出来。是的,我喜欢认为这是解决边缘情况的一个不错的解决方案。
    • 对我来说它不起作用。但是上一个答案中的 inViewport() 在 FF 中工作。
    • 如果您有圆角或应用了变换,检查元素的中心是否可见也可能是有益的,因为边界角可能不会返回预期的元素:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
    • 对我来说输入无效(chrome canary 50)。不知道为什么,也许是原生圆角?我不得不稍微减少坐标以使其工作 el.contains(efp(rect.left+1, rect.top+1)) || el.contains(efp(rect.right-1, rect.top+1)) || el.contains(efp(rect.right-1, rect.bottom-1)) || el.contains(efp(rect.left+1, rect.bottom-1))
    【解决方案6】:

    我试过Dan's answer,但是,用于确定边界的代数意味着元素必须既≤视口大小并且完全在视口内才能得到true,容易导致误报。如果你想确定一个元素是否在视口中,ryanve's answer 很接近,但被测试的元素应该与视口重叠,所以试试这个:

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

    【讨论】:

      【解决方案7】:

      这是我的解决方案。如果元素隐藏在可滚动容器中,它将起作用。

      Here's a demo(尝试调整窗口大小)

      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 加载更多记录功能)。

      【讨论】:

        【解决方案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/

        【讨论】:

        • 我的解决方案更贪婪更快,当元素在视口中有任何像素时,它将返回false。
        • 我喜欢它。简洁的。您可以在第一行删除函数名称和括号之间以及括号和大括号之间的空格。从来不喜欢那些空间。也许只是我的文本编辑器对它进行了颜色编码,仍然使它易于阅读。 function aaa(arg){statements} 我知道这并不能让它执行得更快,而是属于缩小范围。
        • 这与问题所问的完全相反,为什么允许它作为解决方案存在?至少,答案应该说明这个函数可以用来检查元素是否在视口之外,而不是仅仅依靠函数名来暗示这一点。
        【解决方案9】:

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

        顺便说一句,window.innerWidth 和 document.documentElement.clientWidth 之间的the difference 是 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>
        

        【讨论】:

        • isElementPartiallyInViewport 也非常有用。不错。
        • @Arun chauhan:我的代码都没有加载图像,为什么要加载,公式是正确的。
        • @targumon:原因是老浏览器的支持。
        • @StefanSteiger 根据 MDN,它从 IE9 开始就受支持,因此直接使用 window.innerHeight 实际上是安全的(至少在我的情况下)。谢谢!
        • @MCCCS:哈哈,很好!很高兴它对某人有用;)
        【解决方案10】:

        基于dan's solution,我尝试清理实现,以便在同一页面上多次使用它更容易:

        $(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 多个有条件的动画时效果特别好。

        【讨论】:

        • 你绝对应该在滚动处理程序之外缓存$window = $(window)
        【解决方案11】:

        这会检查一个元素是否至少部分在视图中(垂直维度):

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

        【讨论】:

          【解决方案12】:

          我发现这里接受的答案对于大多数用例来说过于复杂。这段代码很好地完成了这项工作(使用 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");
              }
          });
          

          【讨论】:

          • 你绝对应该在滚动处理程序之外缓存$window = $(window)
          【解决方案13】:

          我认为这是一种更实用的方法。 Dan's answer 不能在递归上下文中工作。

          这个函数解决了当你的元素在其他可滚动的 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)
              );
          };
          

          【讨论】:

            【解决方案14】:

            我发现没有以jQuery 为中心的可用功能版本令人不安。当我遇到Dan's solution 时,我发现有机会为喜欢以 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
                }
            });
            

            【讨论】:

            • 花括号是否足以关闭 if 语句?
            • 我无法让它与同一类的多个元素一起工作。
            • @TheWhizofOz 我已经更新了我的答案,以举例说明您提出的其他可能的用例。祝你好运。
            【解决方案15】:

            我有同样的问题,并通过使用 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.');
               }
            };
            

            【讨论】:

              【解决方案16】:

              对于类似的挑战,我真的很喜欢 this gist,它为 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 是指您想知道的元素,例如,overTopoverBottom - 您应该了解一下...

              【讨论】:

                【解决方案17】:

                我在这里遇到的所有答案都只检查元素是否位于当前视口内。但这并不意味着它是可见的
                如果给定元素位于内容溢出的 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 中测试了这个解决方案。

                【讨论】:

                  【解决方案18】:

                  我使用这个函数(它只检查 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;
                      }
                  
                  }
                  

                  【讨论】:

                    【解决方案19】:

                    这是对我有用的简单而小型的解决方案。

                    示例:您想查看该元素是否在具有溢出滚动的父元素中可见。

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

                    【讨论】:

                      【解决方案20】:

                      这是一个函数,它判断一个元素是否在 元素的当前视口中可见:

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

                      【讨论】:

                        【解决方案21】:

                        新的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>

                        【讨论】:

                        • 良好的实现,根据this answer 中的链接,它应该通过将&lt;!DOCTYPE html&gt; 添加到HTML 来在Safari 上工作
                        • 请注意,IntersectionObserver 是一项实验性功能(将来可能会更改)。
                        • @KarthikChintala - 除了 IE 之外的所有浏览器都支持它 - 还有一个 polyfill 可用。
                        • 没有解决 OP 的问题,因为只检测到 更改IntersectionObserver 仅在目标相对于根移动后触发回调。
                        • 当调用observe 时立即触发事件,告诉您当前被跟踪元素的交叉点状态。所以,在某种程度上 - 它解决了。
                        【解决方案22】:

                        IMO 尽可能简单:

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

                        【讨论】:

                          【解决方案23】:

                          最简单的解决方案作为Element.getBoundingClientRect()的支持有become perfect

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

                          【讨论】:

                          • 这在移动浏览器上表现如何?他们中的大多数在视口方面都有问题,它们的标题在滚动时会向上或向下,并且当键盘出现时会有不同的行为,这取决于它是 android 还是 ios 等。
                          • @Kev 应该可以正常工作,具体取决于您何时调用此方法。如果调用它然后调整窗口大小,结果显然可能不再正确。您可以根据您想要的功能类型在每个调整大小事件上调用它。请随时就您的特定用例提出单独的问题,并在此处联系我。
                          • 在 99% 的情况下,这已经足够了,特别是当您只需要启动或停止推子或其他东西并节省一些 CPU 时。将设备定位到死亡的是开发人员,而不是普通用户。 $(window).on('scroll', function(){ if(isInView($('.fader').get(0))) {} else {} });
                          • 在你的情况下,你知道它在那里,但如果它完全在那里,则不是
                          【解决方案24】:

                          以前答案中的大多数用法在这些点上都失败了:

                          -当元素的任何像素可见,但不可见“角落”时,

                          -当元素大于视口且居中时,

                          -他们中的大多数只检查文档或窗口中的单个元素

                          好吧,对于所有这些问题,我都有一个解决方案,而且好处是:

                          -当任何边只显示一个像素并且不是角时,您可以返回visible

                          -当元素大于视口时,您仍然可以返回visible

                          -你可以选择你的parent element或者你可以自动让它选择,

                          -也适用于动态添加的元素

                          如果您检查下面的 sn-ps,您会看到在元素容器中使用 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 中 时,它无法判断它是否可见,因为我们只检查元素是否从 sides 或 角落。

                          而且这个包括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/

                          如果元素的任何部分是否显示在视图中,此代码用于提供更精确的信息。对于性能选项或仅垂直幻灯片,请勿使用此选项!此代码在绘制案例时更有效。

                          【讨论】:

                            【解决方案25】:

                            在 Android 上放大 Google Chrome 时,最常见的答案不起作用。与Dan's answer 结合使用时,要考虑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);
                            

                            【讨论】:

                              【解决方案26】:

                              这里的所有答案都是确定元素是否完全包含在视口中,而不仅仅是以某种方式可见。例如,如果在视图底部只能看到一半的图像,则此处的解决方案将失败,考虑到“外部”。

                              我有一个用例,我正在通过 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));
                              

                              这基本上是检查顶部或底部边界是否在视口中独立。另一端可能在外面,但只要一端在里面,它至少部分是“可见的”。

                              【讨论】:

                                【解决方案27】:

                                这是一个 sn-p 检查给定元素是否在其父元素中完全可见:

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

                                【讨论】:

                                  【解决方案28】:

                                  我们现在有一个原生 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;
                                  }
                                  &lt;div id="el"&gt;this is element&lt;/div&gt;

                                  【讨论】:

                                  • 这应该被选为新的正确答案。
                                  • 精彩回答
                                  • 最好的解决方案,我曾经在这里仍然感兴趣的人使用它设置为添加删除类const observer = new window.IntersectionObserver( ([entry]) =&gt; { if (entry.isIntersecting) { entry.target.classList.add("viewport__active"); return; } entry.target.classList.remove("viewport__active"); }, { root: null, threshold: 0.4 // 0.0 - 1.0 } );
                                  【解决方案29】:

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

                                  【讨论】:

                                    【解决方案30】:

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

                                    【讨论】:

                                      猜你喜欢
                                      • 1970-01-01
                                      • 2010-09-25
                                      • 2021-11-08
                                      相关资源
                                      最近更新 更多