如何判断 DOM 元素在当前视口中是否可见?

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

提问人:benzaita 提问时间:9/24/2008 最后编辑:Peter Mortensenbenzaita 更新时间:11/18/2023 访问量:813553

问:

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

(该问题涉及 Firefox。

javascript html firefox DOM 浏览器

评论

1赞 Andy E 3/4/2013
我添加了自己的解决方案来解决此问题
1赞 Dexygen 5/8/2013
这些解决方案中是否有任何一个考虑了 dom 节点的 z 索引,以及它如何通过可能隐藏具有较低 z 索引的元素来影响可见性?
1赞 thednp 3/21/2015
所提供的答案都不适用于生成的绝对定位元素。
1赞 roryf 9/24/2008
取决于你所说的可见是什么意思。如果你的意思是它当前是否显示在页面上,给定滚动位置,你可以根据元素 y 偏移量和当前滚动位置来计算它。
3赞 leonheess 1/10/2020
有一百万个答案,而且大多数都长得离谱。请参阅此处查看两行代码

答:

421赞 Prestaul 9/24/2008 #1

更新:时间在前进,我们的浏览器也在前进。不再推荐使用此技术,如果您不需要支持 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
  );
}

评论

1赞 Prestaul 9/24/2008
发布的原始功能有错误。在重新分配 el...
32赞 amartynov 3/6/2011
如果元素位于可滚动的 div 中并滚动出视图怎么办?
3赞 Dan 9/26/2011
请查看以下脚本的较新版本
1赞 Eric Nguyen 11/8/2012
也对@amartynov的问题感到好奇。有谁知道如何简单地判断一个元素是否由于祖先元素的溢出而被隐藏?如果无论孩子嵌套的深度如何,都可以检测到这一点,则会有奖励。
1赞 Prestaul 7/6/2015
@deadManN通过 DOM 递归是出了名的慢。这已经足够了,但浏览器供应商也专门为查找元素坐标的目的而创建......我们为什么不使用它呢?getBoundingClientRect
1695赞 31 revs, 11 users 78%Dan #2

现在大多数浏览器都支持 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,则会影响元素的可见性。您应该控制它并手动调用。不幸的是,我们没有任何跨浏览器事件。另一方面,这允许我们进行优化,并仅对可以改变元素可见性的 DOM 修改进行重新检查。handler()onrepaint

永远不要只在jQuery $(document).ready()中使用它,因为目前没有保证CSS已经应用。你的代码可以在硬盘上与你的CSS一起本地工作,但一旦放在远程服务器上,它就会失败。

触发后,将应用样式,但尚未加载图像。因此,我们应该添加事件侦听器。DOMContentLoadedwindow.onload

我们还不能赶上缩放/捏合事件。

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

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

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

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

评论

7赞 Claudio 10/20/2011
我正在使用这个解决方案(不过要小心“botom”错别字)。还有一点需要注意,当我们正在考虑的元素中会有图像时。Chrome(至少)必须等待图像加载,使其具有 boundingRectangle 的确切值。似乎Firefox没有这个“问题”
6赞 agaase 12/8/2013
当您在正文内的容器中启用滚动时,它是否有效。例如,它在这里不起作用 - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport(document.getElementById(“innerele”))。Innerele 存在于启用了滚动的容器中。
98赞 Roonaan 2/21/2014
计算假定元素小于屏幕。如果您有高或宽的元素,则使用起来可能更准确return (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
15赞 jiminy 4/12/2014
提示:对于那些尝试使用jQuery实现这一点的人,只需友好地提醒您传入HTML DOM对象(例如,),而不是jQuery对象(例如,)。jQuery的等价物是像这样添加: .isElementInViewport(document.getElementById('elem'))isElementInViewport($("#elem))[0]isElementInViewport($("#elem)[0])
21赞 Matt W 10/18/2016
el is not defined
50赞 ryanve 9/14/2012 #3

请参阅 verge 的源代码,它使用 getBoundingClientRect。就像:

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

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

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

如果元素的任何部分位于视口中,则返回该值。true

2赞 rainyjune 2/8/2013 #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;
}

评论

12赞 Andy E 3/4/2013
你应该试着解释为什么你的版本更好。就目前而言,它看起来或多或少与其他解决方案相同。
1赞 teter 5/13/2015
很好的解决方案,它有一个 BOX/ScrollContainer,并且不使用 WINDOW(仅当未指定时)。看一下代码,而不是评价它是一个更通用的解决方案(一直在搜索它)
234赞 Andy E 3/4/2013 #5

更新

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

  • 比侦听滚动事件更好的性能
  • 适用于跨域 iframe
  • 可以判断一个元素是否阻碍/与另一个元素相交

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


上一个答案

Dan 提供的答案存在一些问题,可能使其成为某些情况下的不合适方法。其中一些问题在底部附近的回答中被指出,他的代码将对以下元素给出误报:

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

这些局限性在以下简单测试结果中得到了证明:

Failed test, using 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/

结果:

Passed test, using isElementVisible

其他说明

然而,这种方法并非没有其自身的局限性。例如,即使前面的元素实际上并没有隐藏它的任何部分,其 z 指数低于同一位置的另一个元素,但被测试的元素也会被标识为隐藏。尽管如此,这种方法在某些情况下仍有其用途,而 Dan 的解决方案没有涵盖。

两者都是 CSSOM 工作草案规范的一部分,并且至少在 IE 6 及更高版本以及大多数桌面浏览器中支持很长时间(尽管并不完美)。有关详细信息,请参阅有关这些函数的 Quirksmodeelement.getBoundingClientRect()document.elementFromPoint()

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

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

评论

3赞 Kevin Lamping 3/20/2013
您的意思是使用 doc.documentElement.clientWidth 吗?应该改为“document.documentElement”吗?另一方面,这是唯一适用于用例的方法,例如使用 CSS 'clip' 属性隐藏元素的内容以实现可访问性: snook.ca/archives/html_and_css/hiding-content-for-accessibility
3赞 Satya Prakash 12/28/2013
对我来说,这是行不通的。但是上一个答案中的 inViewport() 在 FF 中有效。
14赞 Jared 3/10/2015
如果应用了圆角或变换,检查元素的中心是否可见也可能是有益的,因为边角可能不会返回预期的元素:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
2赞 Rayjax 2/18/2016
没有为我处理输入(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))
1赞 Andy 7/20/2016
@AndyE 这并不总是适用于比视口更宽/更高的元素,因为所有角落都可能在屏幕之外,即使它是可见的
94赞 Walf 4/29/2013 #6

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

function isElementInViewport(el) {
    var rect = el.getBoundingClientRect();

    return rect.bottom > 0 &&
        rect.right > 0 &&
        rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
        rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}
6赞 Ally 2/7/2014 #7

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

下面是一个演示(尝试调整窗口大小)

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 load-more-records 功能)。

评论

0赞 Robert 11/24/2022
仅当元素隐藏在视口下时,此解决方案才有效。当在视口的上侧滚动时,它仍然返回 true。
33赞 Eric Chen 4/23/2014 #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/

评论

4赞 Eric Chen 1/20/2015
我的解决方案更贪婪,速度更快,当元素在视口中有任何像素时,它将返回false。
1赞 Y.K. 9/6/2018
我喜欢。简明。您可以删除第一行的函数名称和括号之间以及括号和大括号之间的空格。从来不喜欢那些空间。也许只是我的文本编辑器对它进行了颜色编码,仍然使它易于阅读。函数 aaa(arg){statements} 我知道这并不能使它执行得更快,而是属于缩小。
1赞 Twyx 9/23/2021
从字面上看,这与问题提出的相反,为什么允许它作为解决方案存在?至少,答案应该说明此函数可用于检查元素是否在视口之外,而不仅仅是依靠函数名称来暗示这一点。
42赞 Stefan Steiger 9/25/2014 #9

作为公共服务:
Dan 的答案是正确的计算(元素可以>窗口,尤其是在手机屏幕上),以及正确的 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>

评论

4赞 RJ Cuthbertson 10/28/2015
isElementPartiallyInViewport也非常有用。好东西。
1赞 Stefan Steiger 8/13/2018
@Arun chauhan:我的代码都没有加载图像,所以为什么要加载图像,而且公式是正确的。
1赞 Stefan Steiger 1/23/2019
@targumon:原因是支持旧浏览器。
1赞 targumon 1/24/2019
@StefanSteiger根据 MDN 的说法,它从 IE9 开始就被支持,所以直接使用 window.innerHeight 实际上是安全的(至少在我的情况下)。谢谢!
1赞 Stefan Steiger 10/4/2021
@MCCCS:哈哈,不错!很高兴它对某人有用;)
4赞 Pirijan 12/27/2014 #10

根据 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+ 个东西要在页面上有条件地制作动画时,效果特别好。

评论

0赞 Adam Rehal 11/1/2017
你绝对应该缓存在滚动处理程序之外$window = $(window)
1赞 Lumic 1/2/2015 #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}
}
10赞 Adam Rehal 1/30/2015 #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");
    }
});

评论

0赞 sam 7/2/2017
您绝对应该在滚动处理程序之外进行缓存。$window = $(window)
6赞 ton 4/24/2015 #13

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

此函数通过递归测试任何级别直到 HTML 标记来解决当您的元素位于其他可滚动 div 中时的问题,并在第一个 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)
    );
};
26赞 r3wt 8/2/2015 #14

我发现令人不安的是,没有以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
    }
});

评论

1赞 calasyr 5/25/2016
大括号是否足以关闭 if 语句?
1赞 The Whiz of Oz 6/20/2017
我无法使它与同一类的多个元素一起使用。
1赞 r3wt 6/21/2017
@TheWhizofOz我已经更新了我的答案,以举例说明您提出的其他可能的用例。祝你好运。
2赞 www139 9/13/2015 #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.');
   }
};
-2赞 Philzen 3/14/2016 #16

对于类似的挑战,我真的很喜欢这个要点,它公开了 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赞 Domysee 6/24/2016 #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 中测试了此解决方案。

评论

0赞 mixalbl4 1/28/2022
如果有一个函数,它不仅可以返回给定元素是否可见的事实,还可以返回其可见区域的平方,例如:visibleRectOfElement(el) => {top: 15, left: 45, right: 550, bottom: 420}
0赞 Sander Jonk 1/13/2017 #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;
    }

}
1赞 Stevan Tosic 6/19/2017 #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');
     }
})
2赞 ssten 9/28/2018 #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
    );
}
28赞 Randy Casburn 3/15/2019 #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>

评论

3赞 Alon Eitan 3/15/2019
良好的实现,根据此答案中的链接,它应该通过添加到 HTML 来在 safari 上运行<!DOCTYPE html>
0赞 Karthik Chintala 6/26/2019
请注意,这是一项实验性功能(将来可能会更改)。IntersectionObserver
2赞 Randy Casburn 6/26/2019
@KarthikChintala - 除 IE 之外的所有浏览器都支持它 - 并且还有一个可用的 polyfill。
0赞 Brandon Hill 8/29/2019
不解决 OP 的问题,因为仅检测更改:仅在目标相对于根移动后触发回调。IntersectionObserver
0赞 Mesqalito 1/24/2020
调用事件时,会立即触发,告诉您被跟踪元素的当前交集状态。所以,在某种程度上 - 它解决了。observe
2赞 JuanM. 3/15/2019 #22

尽可能简单,IMO:

function isVisible(elem) {
  var coords = elem.getBoundingClientRect();
  return Math.abs(coords.top) <= coords.height;
}
25赞 leonheess 7/31/2019 #23

最简单的解决方案,因为对 Element.getBoundingClientRect() 的支持已经变得完美

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

评论

0赞 Kev 1/24/2020
这在移动浏览器上表现如何?它们中的大多数在视口方面都存在错误,它们的标题在滚动时会向上或向下移动,并且当键盘出现时会有不同的行为,具体取决于它是 android 还是 ios 等。
0赞 leonheess 1/26/2020
@Kev应该可以正常工作,具体取决于何时调用此方法。如果调用它,然后调整窗口大小,则结果显然可能不再正确。你可以在每个 resize-event 上调用它,具体取决于你想要的功能类型。请随时提出有关您的特定用例的单独问题,并在此处联系我。
0赞 Jonas Lundman 5/7/2021
在 99% 的情况下,这就足够了,特别是如果您只需要启动或停止推子或其他东西并节省一些 CPU。是开发人员将设备定位到死亡,而不是普通用户。$(window).on('scroll', function(){ if(isInView($('.fader').get(0))) {} else {} });
4赞 Berker Yüceer 9/12/2019 #24

前面答案中的大多数用法在以下几点上都失败了:

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

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

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

好吧,对于所有这些问题,我有一个解决方案,优点是:

- 当任何侧面的像素只显示并且不是角落时,您可以返回,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>

你看,当你在元素3里面时,它无法判断它是否可见,因为我们只检查元素从侧面角落是否可见。

这个包括 crossSearchAlgorithm,它允许你在元素大于视口时仍然返回可见

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/

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

5赞 Dakusan 11/17/2019 #25

在 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);
1赞 Chris Pratt 3/12/2020 #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));

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

1赞 cryss 4/16/2021 #27

下面是一个代码片段,用于检查给定元素在其父元素中是否完全可见:

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
  );
}
100赞 Ismail Farooq 7/6/2021 #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;
}
<div id="el">this is element</div>

评论

19赞 Bondsmith 9/5/2021
这应该被选为新的正确答案。
1赞 nima 10/28/2021
精彩的回答
0赞 Dan W 11/15/2021
对于那些仍然对这里感兴趣的人,我曾经使用过的最佳解决方案,它被设置为添加删除类const observer = new window.IntersectionObserver( ([entry]) => { 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 } );
1赞 Hans-Jürgen Petrich 8/28/2022
问题的最佳答案
1赞 Raheel Khan 8/27/2023
简单易行的答案
-1赞 dirck 10/4/2021 #29

Domysee https://stackoverflow.com/a/37998526 的答案接近正确。

许多示例使用“完全包含在视口中”,他的代码使用百分比来允许部分可见。他的代码还解决了大多数示例忽略的“是否是父级剪裁视图”问题。

缺少的一个元素是父级滚动条的影响 - 返回父级的外部矩形,其中包括滚动条,而不是内部矩形,后者不包括滚动条。子项可以隐藏在父滚动条后面,并在不显示时被视为可见。getBoundingClientRect

推荐的观察者模式不适合我的用例:使用箭头键更改表中当前选定的行,并确保新选择可见。为此使用观察者会过于复杂。

这里有一些代码 -

它包括一个额外的 hack (),因为我的表有一个粘性标题,无法通过直接的方式检测到(并且自动处理它会非常乏味)。此外,它使用十进制(0 到 1)而不是百分比来表示所需的可见分数。(就我而言,我需要完整的 y,而 x 无关紧要)。fudgeY

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;
}
7赞 Arthur Shlain 12/25/2021 #30

ViewportInfo

/**
 * 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;
}
2赞 Ynod 10/13/2022 #31
 const isHTMLElementInView = (element: HTMLElement) => {
  const rect = element?.getBoundingClientRect()

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

此函数检查元素是否在垂直级别的视口中。

评论

0赞 LukasKroess 6/1/2023
事实上,在许多情况下,Y 是你唯一关心的轴