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


949

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

(问题是针对Firefox的。)


取决于可见的含义。如果您的意思是当前显示在页面上,则在给定滚动位置的情况下,您可以根据元素y偏移量和当前滚动位置进行计算。
roryf

18
清晰度:可见,如在当前显示的矩形内。可见,如未隐藏或显示:无?可见,就像不在Z顺序的其他后面吗?
亚当·赖特

6
就像在当前显示的矩形中一样。谢谢。改写。
benzaita

2
请注意,此处的所有答案将为您提供在其父元素的可见区域之外(隐藏了溢出的eh)但在视口区域之内的项目的误报。
Andy E

1
有一百万个答案,而且大多数答案太长了。看到这里的两班轮
leonheess

Answers:


346

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

15
如果该元素位于可滚动的div中并从视图中滚动出来,该怎么办?
amartynov 2011年

2
请查看以下脚本的较新版本
丹,

1
也对@amartynov的问题感到好奇。有谁知道如何简单地判断某个元素是否由于祖先元素的溢出而被隐藏?如果无论孩子的嵌套深度如何,都可以检测到这种情况。
埃里克·阮

1
众所周知,通过DOM递归的@deadManN很慢。这是足够的理由,但是浏览器供应商还getBoundingClientRect专门创建了查找元素坐标的目的……我们为什么不使用它呢?
Prestaul 2015年

1402

现在,大多数浏览器都支持getBoundingClientRect方法,这已成为最佳实践。使用旧的答案非常慢不够准确,并且存在多个错误

选择正确的解决方案几乎永远都不是精确的。您可以阅读有关其错误的更多信息。


此解决方案已在Internet Explorer 7(及更高版本),iOS 5(及更高版本)Safari,Android 2.0(Eclair)及更高版本,BlackBerry,Opera Mobile和Internet Explorer Mobile 9上进行了测试


function isElementInViewport (el) {

    // Special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
    );
}

如何使用:

您可以确定上面给出的函数在被调用时会返回正确的答案,但是如何将元素作为事件进行跟踪呢?

将以下代码放在<body>标签的底部:

function onVisibilityChange(el, callback) {
    var old_visible;
    return function () {
        var visible = isElementInViewport(el);
        if (visible != old_visible) {
            old_visible = visible;
            if (typeof callback == 'function') {
                callback();
            }
        }
    }
}

var handler = onVisibilityChange(el, function() {
    /* Your code go here */
});


// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);

/* // Non-jQuery
if (window.addEventListener) {
    addEventListener('DOMContentLoaded', handler, false);
    addEventListener('load', handler, false);
    addEventListener('scroll', handler, false);
    addEventListener('resize', handler, false);
} else if (window.attachEvent)  {
    attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

如果您进行任何DOM修改,它们当然可以更改元素的可见性。

准则和常见陷阱:

也许您需要跟踪页面缩放/移动设备挤压?jQuery应该处理缩放/收缩交叉浏览器,否则第一个第二个链接应该会对您有所帮助。

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

永远不要只在jQuery $(document).ready()中使用它,因为目前没有担保CSS。您的代码可以在硬盘上与CSS一起在本地使用,但是一旦放在远程服务器上,它将失败。

之后DOMContentLoaded被解雇,样式应用于,但图像尚未加载。因此,我们应该添加window.onload事件监听器。

我们尚无法捕获缩放事件。

最后的办法可能是以下代码:

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

如果您关心网页中的标签是否处于活动状态且可见,则可以使用HTML5 API 的强大功能pageVisibiliy

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


6
我正在使用此解决方案(但是请注意“底部”拼写错误)。还有一点要注意,我们正在考虑的元素何时会包含图像。Chrome(至少)必须等待图像加载完毕,以具有boundingRectangle的确切值。似乎Firefox没有这个“问题”
Claudio

4
当您在体内的容器中启用了滚动功能时,它是否起作用?例如,它在这里不起作用 -agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport(document.getElementById(“ innerele”))。innerele存在于启用了滚动的容器内。
agaase 2013年

70
计算假定该元素小于屏幕。如果您的元素较高或较宽,则使用起来可能更准确return (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan 2014年

11
提示:对于那些尝试使用jQuery实现此功能的用户,只需友好地提醒您传入HTML DOM对象(例如isElementInViewport(document.getElementById('elem')))而不是jQuery对象(例如isElementInViewport($("#elem)))。jQuery等效项是这样添加的[0]isElementInViewport($("#elem)[0])
jiminy 2014年

11
el is not defined
M_Willett '16

175

更新资料

在现代浏览器中,您可能想查看Intersection Observer API,它具有以下优点:

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

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


先前的答案

Dan提供答案存在一些问题,可能使它不适用于某些情况。他在底部的答案中指出了其中的一些问题,他的代码将给以下元素带来误报:

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

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

使用isElementInViewport的测试失败

解决方案: isElementVisible()

这是解决这些问题的方法,下面是测试结果,并对代码的某些部分进行了说明。

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || document.documentElement.clientWidth,
        vHeight  = window.innerHeight || document.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

通过测试: http //jsfiddle.net/AndyE/cAY8c/

结果:

使用isElementVisible通过测试

补充笔记

但是,此方法并非没有其自身的局限性。例如,即使在前面的元素实际上没有隐藏其任何部分的情况下,使用比同一位置的另一个元素更低的z-index测试的元素也将被标识为隐藏。不过,这种方法在某些情况下仍然有用,但Dan的解决方案却无法解决。

两者element.getBoundingClientRect()document.elementFromPoint()是CSSOM工作草案规范的一部分,并且在至少IE 6和更高的支持和长一段时间的桌面浏览器(尽管,不完全)。有关更多信息,请参见这些功能的Quirksmode

contains()用于查看返回document.elementFromPoint()的元素是否是我们正在测试可见性的元素的子节点。如果返回的元素是同一元素,则它也返回true。这只会使检查更可靠。所有主要浏览器均支持该功能,Firefox 9.0是它们最后添加的浏览器。要获得较早的Firefox支持,请查看此答案的历史记录。

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


2
您是要使用doc.documentElement.clientWidth吗?应该是“ document.documentElement”吗?另外,这是唯一的方法,也适用于用例,例如使用CSS'clip
Kevin Lamping

@klamping:很好,谢谢。我已经从我doc用作别名的代码中复制了该代码document。是的,我想将其视为针对极端情况的一个不错的解决方案。
Andy E

2
对我来说,这是行不通的。但是先前答案中的inViewport()在FF中有效。
萨蒂亚·普拉卡什

9
如果您已圆角或应用了变换,则检查元素的中心是否可见可能也是有益的,因为边界角可能无法返回预期的元素:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared

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

65

我尝试了丹的答案但是,用于确定边界的代数意味着该元素必须既≤视口大小,又必须完全在视口内部才能获取true,很容易导致假阴性。如果要确定某个元素是否在视口中,则ryanve的答案很接近,但是要测试的元素应与该视口重叠,因此请尝试以下操作:

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

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

33

作为一项公共服务:
Dan的答案带有正确的计算(元素可以是> window,尤其是在手机屏幕上),并且正确的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>

2
isElementPartiallyInViewport也是非常有用的 好一个。
RJ卡斯伯森2015年

对于isElementInViewport(e):您的代码甚至没有加载图像,这是正确的:function isElementInViewport(e){var t = e.getBoundingClientRect(); 返回t.top> = 0 && t.left> = 0 && t.bottom <=(window.innerHeight || document.documentElement.clientHeight)&& t.right <=(window.innerWidth || document.documentElement.clientWidth) }
Arun chauhan

1
@Arun chauhan:我的代码都没有加载图像,为什么要加载图像,并且公式正确。
Stefan Steiger

1
@targumon:原因是对旧浏览器的支持。
Stefan Steiger

1
根据MDN,@ StefanSteiger从IE9开始就受到支持,因此直接使用window.innerHeight实际上是安全的(至少在我的情况下)。谢谢!
targumon

28

请参阅使用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如果元素的任何部分在视口中,则返回。


27

我的简短快速版本:

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
我的解决方案更加贪婪且速度更快,当element在视口中具有任何像素时,它将返回false。
Eric Chen

1
我喜欢。简洁。您可以在第一行中删除函数名称和括号之间以及括号和括号之间的空格。从来没有喜欢那些空间。也许仅仅是我的文本编辑器对所有内容进行了颜色编码,仍然使它易于阅读。函数aaa(arg){statements}我知道这并不能使它执行得更快,而是要缩小。
YK

21

我发现没有可用的以jQuery为中心的功能令人不安。当我遇到Dan的解决方案时,我发现有机会为喜欢以jQuery OO风格进行编程的人们提供一些东西。它既好看又活泼,对我来说就像一种魅力。

Bada Bing Bada繁荣

$.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
花括号足以关闭if语句吗?
calasyr

1
我无法使其与同一个类的多个元素一起使用。
的《绿野仙踪》

1
@TheWhizofOz我已经更新了答案,以举例说明您提出的其他可能的用例。祝你好运。
r3wt

10

新的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
良好的实现,并且根据此答案中的链接,它应该通过添加<!DOCTYPE html>到HTML来在safari中工作
Alon Eitan

请注意,这IntersectionObserver是一项实验性功能(将来可能会更改)。
Karthik Chintala '19

2
@KarthikChintala-除IE之外的所有浏览器均支持它-并且还有一个polyfill可用。
兰迪·卡斯本

由于仅检测到更改,因此未解决OP的问题:IntersectionObserver仅在目标相对于根移动之后才触发回调。
布兰登·希尔

当调用observe事件被触发时,立即告诉您被跟踪元素的当前交集状态。因此,以某种方式-它解决了。
Mesqalito

8

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


8

我发现,对于大多数用例来说,这里可接受的答案过于复杂。这段代码很好地完成了工作(使用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)在滚动处理程序之外缓存。
山姆

4

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

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

这在移动浏览器上如何表现?他们大多是越野车就视,有或它们的头往上走下来滚动和不同的行为时,键盘显示出来,这取决于它是否在Android或iOS等
千电子伏

@Kev应该可以正常工作,具体取决于您何时调用此方法。如果调用它,然后调整窗口大小,则结果可能显然不再正确。您可以在每个调整大小事件上调用它,具体取决于所需的功能类型。随意问一个关于您的特定用例的单独问题,然后在此处ping我。
leonheess

3

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

当您的元素位于其他可滚动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)
    );
};

3

在Android上放大Google Chrome时,最不能接受的答案无效。结合Dan的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);

2

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

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

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


2

基于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关键帧动画的类。它非常简单明了,并且当您有十多个东西要在页面上进行有条件的动画处理时,效果特别好。


您绝对应该缓存$window = $(window)在滚动处理程序之外
Adam Rehal '17

2

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

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

-当元素大于视口并居中时

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

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

-您可以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内部像素以查看:

您会看到,当您在element3内部时,它无法告诉您是否可见,因为我们仅检查该元素是否从侧面角落可见。

并且其中包括crossSearchAlgorithm一个允许您visible在元素大于视口时仍返回的功能:

要使用的JSFiddle:http//jsfiddle.net/BerkerYuceer/grk5az2c/

无论元素的任何部分是否在视图中显示,都将使用此代码来获取更精确的信息。对于性能选项或仅垂直幻灯片,请勿使用此功能!此代码在绘图情况下更有效。


1

更好的解决方案:

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

1
很好的解决方案,它具有BOX / ScrollContainer,并且不使用WINDOW(仅当未指定时)。看一看代码,而不是评价它是一个更通用的解决方案(正在大量搜索)
teter 2015年

1

IMO尽可能简单:

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

0

这是一个告诉元素在元素的当前视口中是否可见的函数:

function inParentViewport(el, pa) {
    if (typeof jQuery === "function"){
        if (el instanceof jQuery)
            el = el[0];
        if (pa instanceof jQuery)
            pa = pa[0];
    }

    var e = el.getBoundingClientRect();
    var p = pa.getBoundingClientRect();

    return (
        e.bottom >= p.top &&
        e.right >= p.left &&
        e.top <= p.bottom &&
        e.left <= p.right
    );
}

0

这将检查元素是否至少部分在视图中(垂直尺寸):

function inView(element) {
    var box = element.getBoundingClientRect();
    return inViewBox(box);
}

function inViewBox(box) {
    return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}


function getWindowSize() {
    return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}

0

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

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

此代码仅检查它是否在视口中垂直,而不是水平。在这种情况下,变量(数组)“元素”包含要检查的所有元素,这些元素在视口中是垂直的,因此请在任何位置抓取所需的任何元素并将其存储在其中。

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

由于代码是通用的,因此您将想知道视口中的“哪个”元素。为了找出答案,您可以通过自定义属性,节点名称,ID,类名称等进行确定。

这是我的代码(如果不起作用,请告诉我;它已在Internet Explorer 11,Firefox 40.0.3,Chrome版本45.0.2454.85 m,Opera 31.0.1889.174和Edge with Windows 10中进行了测试,[尚未使用Safari ])...

// Scrolling handlers...
window.onscroll = function(){
  var elements = document.getElementById('whatever').getElementsByClassName('whatever');
  for(var i = 0; i != elements.length; i++)
  {
   if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
      elements[i].getBoundingClientRect().top > 0)
   {
      console.log(elements[i].nodeName + ' ' +
                  elements[i].className + ' ' +
                  elements[i].id +
                  ' is in the viewport; proceed with whatever code you want to do here.');
   }
};

0

这是对我有用的简便解决方案。

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

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

     var container = $('#sidebar');
     var containerHeight = container.height();
     var scrollPosition = $('#row1').offset().top - container.offset().top;

     if (containerHeight < scrollPosition) {
         console.log('not visible');
     } else {
         console.log('visible');
     }
})

0

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

我有一个用例,其中我正在通过进行延迟加载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

我使用此功能(因为大多数时候不需要x,所以它仅检查y是否在屏幕上)

function elementInViewport(el) {
    var elinfo = {
        "top":el.offsetTop,
        "height":el.offsetHeight,
    };

    if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
        return false;
    } else {
        return true;
    }

}

-3

对于类似的挑战,我真的很喜欢这个要点该要点公开了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只是应该获取漂移值...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.