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

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

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

(问题是指火狐)


现在大多数浏览器都支持getboundingclientrect方法,这已经成为最佳实践。使用一个旧的答案是非常缓慢的,不准确的,有几个错误。

选择正确的解决方案几乎从不精确。您可以阅读更多关于它的bug的信息。

此解决方案在IE7+、IOS5+Safari、Android2+、BlackBerry、Opera Mobile和IE Mobile 10上进行了测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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() */
    );
}

如何使用:

您可以确保上面给出的函数在调用时返回正确的答案,但是跟踪元素作为事件的可见性如何?

将以下代码放在标记的底部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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); // IE9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

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

指导方针和常见缺陷:

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

如果修改dom,它会影响元素的可见性。您应该控制它并手动呼叫handler()。不幸的是,我们没有跨浏览器onrepaint事件。另一方面,它允许我们进行优化,并且只对可以更改元素可见性的DOM修改执行重新检查。

永远不要在jquery$(document.ready()中使用它,因为此时没有应用任何CSS担保。您的代码可以在硬盘上本地使用您的CSS,但一旦放到远程服务器上,它就会失败。

启动DOMContentLoaded后,应用样式,但尚未加载图像。因此,我们应该添加window.onload事件侦听器。

我们还无法捕获缩放/收缩事件。

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

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

如果您关心网页上的选项卡是否处于活动状态和可见状态,则可以使用Awesome功能页Visibility HTML5 API。

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

  • 使用z-index重叠
  • 在元素容器中使用EDOCX1[6]

  • 尝试一些新的东西https://pawelgrzybek.com/the-intersection-observer-api-explained/


更新:时间在前进,我们的浏览器也在前进。不再推荐此技术,如果不需要支持ie<7,则应使用下面的@dan's解决方案(https://stackoverflow.com/a/7557433/5628)。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)
  );
}

您可以简单地修改它以确定元素的任何部分是否在视区中可见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
  );
}


更新

在现代浏览器中,您可能希望查看交叉口观察器API,它提供了以下好处:

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

交集观察员正在成为一个成熟的标准,已经在Chrome51+、Edge 15+和Firefox 55+中得到支持,并且正在为Safari开发中。还提供了一种PolyFill。

上一个答案

丹提供的答案存在一些问题,这可能使它在某些情况下成为不合适的方法。这些问题中有一些是在他接近底部的答案中指出的,他的代码将为以下元素提供误报:

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

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

Failed test, using isElementInViewport

解决办法:isElementVisible()

下面是这些问题的解决方案,测试结果如下,并解释了代码的某些部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.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))
    );
}

Passing test: http://jsfiddle.net/AndyE/cAY8c/

结果是:

氧化镁

附加说明

然而,这种方法并非没有其自身的局限性。例如,与同一位置的另一个元素相比,使用较低的z索引测试的元素将被标识为隐藏,即使前面的元素实际上没有隐藏它的任何部分。不过,这种方法在某些情况下也有它的用途,但丹的解决方案并没有涵盖。

element.getBoundingClientRect()document.elementFromPoint()都是CSSOM工作草案规范的一部分,并且至少在IE 6和更高版本以及大多数桌面浏览器中得到长期支持(尽管不是完全支持)。有关更多信息,请参阅这些函数的quirksmode。

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

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


我试过丹的答案,但是用于确定边界的代数意味着元素必须同时小于或等于视区大小,并且完全在视区内才能得到true,很容易导致错误的否定。如果要确定元素是否在视区中,Ryanve的答案很接近,但要测试的元素应与视区重叠,请尝试以下操作:

1
2
3
4
5
6
7
8
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() */;
}


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

顺便说一下,window.innerwidth和document.documentelement.clientwidth的区别在于clientwidth/clientheight不包括滚动条,window.innerwidth/height包含滚动条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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);
}

测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<!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="">
    Test
    <!--
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js">    
    <script src="scrollMonitor.js">
    -->

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

   

</head>
<body>

   

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

       
       
        t
       

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

   

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

   
    -->
</body>
</html>


有一个名为inview的jquery插件可以完成该任务


请参阅使用getboundingclientrect的verge源。就像是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


我的更短更快的版本。

1
2
3
4
function isElementOutViewport(el){
    var rect = el.getBoundingClientRect();
    return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}

根据需要添加jsiddlehttps://jsfiddle.net/on1g619l/1/


我发现不存在以jQuery为中心的可用功能版本,这很麻烦。当我遇到丹的解决方案时,我发现有机会为那些喜欢用jQueryoo风格编程的人提供一些东西。一定要向上滚动并在丹的代码上留下一个向上投票。它又漂亮又活泼,对我来说很有魅力。

八大冰八大潮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$.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
//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();
});

使用

1
2
3
4
5
6
7
$(window).on('scroll',function(){

    if( $('footer').inView() ) {
        // do cool stuff
    }

});


我发现这里接受的答案对于大多数用例来说过于复杂。此代码(使用jquery)可以很好地完成工作,并区分完全可见和部分可见元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var element         = $("#element");
var topOfElement    = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window         = $(window);

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

    var scrollTopPosition   = $window.scrollTop()+$window.height();
    var windowScrollTop     = $window.scrollTop()

    if( windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
       // Element is partially visible (above viewable area)
       console.log("Element is partially visible (above viewable area)");

    }else if( windowScrollTop > bottomOfElement && windowScrollTop > topOfElement ) {
        // Element is hidden (above viewable area)
       console.log("Element is hidden (above viewable area)");

    }else if( scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement ) {
        // Element is hidden (below viewable area)
        console.log("Element is hidden (below viewable area)");

    }else if( scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement ) {
        // Element is partially visible (below viewable area)
        console.log("Element is partially visible (below viewable area)");

    }else{
        // Element is completely visible
        console.log("Element is completely visible");
    }
});


我在这里遇到的所有答案都只检查元素是否位于当前视区内。但这并不意味着它是可见的。如果给定的元素在一个包含溢出内容的DIV中,并且滚动到视图之外,该怎么办?

要解决这个问题,您必须检查元素是否由所有父元素包含。我的解决方案就是这样:

它还允许您指定元素的可见程度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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中测试过这个解决方案。


新的交叉口观察器API非常直接地解决了这个问题。

这个解决方案需要一个多填充,因为Safari,Opera和IE还不支持这个。(解决方案中包含polyfill)。

在这个解决方案中,有一个不可见的框是目标(观察到的)。当它进入视图时,标题顶部的按钮被隐藏。一旦盒子离开视图,它就会显示出来。

1
2
3
4
5
6
7
8
9
10
11
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'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

1
2
3
4
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver">
<header>
  <button>NAVIGATION BUTTON TO HIDE</button>
</header>


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

这个函数通过递归地测试HTML标记上的任何级别来解决当元素位于其他可滚动div中时的问题,并在第一个false中停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 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)
    );
};


基于上面的@dan's解决方案(https://stackoverflow.com/a/7557433/5628),我尝试了清理实现,以便在同一页上多次使用它更容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$(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个以上的内容需要有条件地进行动画制作时尤其有效。

希望有帮助!


尽可能简单地实现IMO:

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


这是我的解决方案,如果一个元素隐藏在一个可滚动的容器中,它就会工作。

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

1
2
3
4
5
6
7
8
9
10
11
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 its within the document viewport
    return top <= document.documentElement.clientHeight;
};

我只需要检查它在Y轴上是否可见(对于滚动Ajax加载更多记录功能)。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
$(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');
     }
})


我有同样的问题,并通过使用getboundingclientrect()解决了它。这段代码是完全"通用"的,只需要编写一次代码就可以工作(您不必为要知道的每个元素都在视区中编写它)。此代码只检查它是否在视区中垂直,而不是水平。在这种情况下,变量(数组)"elements"保存要在视区中垂直检查的所有元素,因此可以在任何位置抓取任何需要的元素并将其存储在该位置。"for循环"循环遍历每个元素,并检查它是否在视区中垂直。每次用户滚动时都会执行此代码!如果getboudingclientrect().top小于视区的3/4(视区中的元素是四分之一),则它注册为"在视区中"。因为代码是通用的,所以您需要知道"哪个"元素在视区中。要了解这一点,您可以通过自定义属性、节点名、ID、类名等来确定它。这是我的代码(告诉我,如果它不起作用,它已经在IE 11、Firefox 40.0.3、Chrome 45.0.2454.85 m、Opera 31.0.1889.174和Edge with Windows 10中测试过,【还没有Safari】)。

1
2
3
4
5
6
7
8
9
10
//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.');
   }
};

希望这能帮助别人:-)


更好的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;    
}


下面是一个函数,用于指示某个元素是否在父元素的当前视区中可见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
    );
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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}
}


我使用这个功能(它只检查Y是否在屏幕上,因为大多数时候不需要X)

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
    }

}

对于类似的挑战,我真的很喜欢这个要点,它为ScrollIntoviewIfneeded()公开了一个polyfill。

所有需要回答的武功都在这个街区内:

1
2
3
4
5
6
7
8
9
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——应该得到漂移…