检查用户是否已滚动到底部(不仅是窗口,而是任何元素)

781
我正在制作一个分页系统(类似Facebook),用户滚动到底部时会加载内容。我想最好的方法是找到用户是否在页面底部,并运行Ajax查询以加载更多帖子。
唯一的问题是我不知道如何检查用户是否已滚动到页面底部。有什么建议吗?
我正在使用jQuery,所以请提供使用它的答案。

对于React.js解决方案,这个链接可能会有所帮助:https://dev59.com/p1QJ5IYBdhLWcg3wwYv7#53158893 - raksheetbhat
32个回答

1117

使用window上的.scroll()事件,如下所示:

$(window).scroll(function() {
   if($(window).scrollTop() + $(window).height() == $(document).height()) {
       alert("bottom!");
   }
});

你可以在这里测试它,它获取窗口的顶部滚动量,加上可见窗口的高度,并检查是否等于整个内容(document)的高度。如果您想检查用户是否接近底部,则代码大致如下:

$(window).scroll(function() {
   if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
       alert("near bottom!");
   }
});

你可以在这里测试该版本,只需将100调整为您想要触发的距离底部的像素即可。


202

简而言之;

Math.abs(element.scrollHeight - element.scrollTop - element.clientHeight) < 1

概念

从本质上讲,“滚动到底部”是指滚动区域(scrollHeight)减去可见内容距离顶部的距离(scrollTop)等于可见内容的高度(clientHeight)的时刻。

换句话说,当这个等式成立时,我们就“滚动”了:

scrollHeight - scrollTop - clientHeight === 0

Diagram showing arrows and boxes for the scrollable area, client visible height and distance from the top in a graphical browser.

防止舍入误差

正如提到的其中一些属性被舍入了,这意味着在scrollTop具有小数部分或舍入值对齐不良的情况下,相等性可能会失败。

可以通过将绝对差与可容忍的阈值进行比较来缓解这个问题:

Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) < 1

一个防止舍入误差的片段可能看起来像这样:

document.getElementById('constrained-container').addEventListener('scroll', event => {
    const {scrollHeight, scrollTop, clientHeight} = event.target;

    if (Math.abs(scrollHeight - clientHeight - scrollTop) < 1) {
        console.log('scrolled');
    }
});
#constrained-container {
  height: 150px;
  overflow-y: scroll;
}

#very-long-content {
  height: 600px;
}
<div id="constrained-container">
  <div id="very-long-content">
    scroll me to the bottom
  </div>
</div>

请注意,我添加了一个比其容器大得多的div来强制滚动,但没有必要在另一个元素中“包裹”内容,直接在元素中放置文本会导致元素溢出。

防抖、延迟和节流

我对此有越来越多的理解,并且发现它超出了这个答案的范围(这个Code Review问题及其答案以及这篇相关文章很有意思),但在特定情况下(如果处理程序进行了昂贵的计算,如果我们将动画与滚动事件绑定,如果我们只想在滚动结束时触发事件,或者任何可能需要的情况),可以很有用:

  • 防抖(当第一次滚动发生时触发处理程序,然后防止它过快触发)
  • 延迟(直到滚动事件在一段时间内没有被触发,才执行处理程序。在ECMAScript上下文中,这通常被称为防抖)
  • 或者节流(防止处理程序在一段时间内触发多次)

在选择执行这些操作时必须非常小心,例如限制事件可能会防止最后一次滚动触发,从而完全破坏无限滚动。

大多数情况下,不执行这三个操作也可以正常工作,因为只需检查是否已完全滚动相对较便宜。


29
Math.abs(element.scrollHeight - element.scrollTop - element.clientHeight) <= 3.0(将 3.0 替换为您认为适合您情况的像素公差)。这么做是因为(A) clientHeightscrollTopclientHeight都是四舍五入的,如果它们全部对齐,可能会导致 3 像素的误差,(B) 用户很难看到 3 像素的差异,所以当用户 "认为" 他们已经滑动到页面底部时实际上并没有到达底部时,用户可能会认为您的网站有问题,(C) 某些设备(特别是无鼠标的设备)在滚动时可能会有奇怪的行为。 - Jack G

126

Nick Craver的回答很好用,只是$(document).height()的值因浏览器而异。

为了让它在所有浏览器上都能正常工作,请使用James Padolsey的这个函数:

function getDocHeight() {
    var D = document;
    return Math.max(
        D.body.scrollHeight, D.documentElement.scrollHeight,
        D.body.offsetHeight, D.documentElement.offsetHeight,
        D.body.clientHeight, D.documentElement.clientHeight
    );
}

$(document).height()替换为$(window).height(),最终代码如下:

$(window).scroll(function() {
       if($(window).scrollTop() + $(window).height() == getDocHeight()) {
           alert("bottom!");
       }
   });

41

除了来自Nick Craver的优秀回答answer from Nick Craver,您还可以对滚动事件进行节流,从而提高浏览器性能

var _throttleTimer = null;
var _throttleDelay = 100;
var $window = $(window);
var $document = $(document);

$document.ready(function () {

    $window
        .off('scroll', ScrollHandler)
        .on('scroll', ScrollHandler);

});

function ScrollHandler(e) {
    //throttle event:
    clearTimeout(_throttleTimer);
    _throttleTimer = setTimeout(function () {
        console.log('scroll');

        //do work
        if ($window.scrollTop() + $window.height() > $document.height() - 100) {
            alert("near bottom!");
        }

    }, _throttleDelay);
}

37

尼克·克雷弗的回答需要稍作修改才能在iOS 6 Safari Mobile上正常工作,应该是这样的:

$(window).scroll(function() {
   if($(window).scrollTop() + window.innerHeight == $(document).height()) {
       alert("bottom!");
   }
});

$(window).height()更改为window.innerHeight应该这样做,因为当地址栏隐藏时,窗口的高度会增加额外的60像素,但是使用$(window).height()不反映此更改,而使用window.innerHeight则可以。

注意window.innerHeight属性还包括水平滚动条的高度(如果呈现),而$(window).height()不会包括水平滚动条的高度。这在移动Safari中不是问题,但可能会导致其他浏览器或未来版本的移动Safari出现意外行为。将==更改为>=可以解决大多数常见用例的问题。

了解有关window.innerHeight属性的更多信息在此处


30

这里有一个相当简单的方法

const didScrollToBottom = elm.scrollTop + elm.clientHeight == elm.scrollHeight

示例

elm.onscroll = function() {
    if(elm.scrollTop + elm.clientHeight == elm.scrollHeight) {
        // User has scrolled to the bottom of the element
    }
}

其中elm是从document.getElementById获取的元素。


25
请检查这个答案
 window.onscroll = function(ev) {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
       console.log("bottom");
    }
};

您可以执行 footerHeight - document.body.offsetHeight 来判断是否接近页脚或已到达页脚。


16

这里有一段代码可以帮助您调试程序。我测试了上面的答案,发现它们有漏洞。我在 Chrome、Internet Explorer、Firefox、 iPad ( Safari) 上进行了测试。我没有其他浏览器可以进行测试...

<script type="text/javascript">
   $(function() {
      $(window).scroll(function () {
         var docElement = $(document)[0].documentElement;
         var winElement = $(window)[0];

         if ((docElement.scrollHeight - winElement.innerHeight) == winElement.pageYOffset) {
            alert('bottom');
         }
      });
   });
</script>

可能有更简单的解决方案,但我停在了它能工作的那一点。

如果您仍然遇到某些问题浏览器,请使用以下代码进行调试:

<script type="text/javascript">
   $(function() {
      $(window).scroll(function () {
         var docElement = $(document)[0].documentElement;
         var details = "";
         details += '<b>Document</b><br />';
         details += 'clientHeight:' + docElement.clientHeight + '<br />';
         details += 'clientTop:' + docElement.clientTop + '<br />';
         details += 'offsetHeight:' + docElement.offsetHeight + '<br />';
         details += 'offsetParent:' + (docElement.offsetParent == null) + '<br />';
         details += 'scrollHeight:' + docElement.scrollHeight + '<br />';
         details += 'scrollTop:' + docElement.scrollTop + '<br />';

         var winElement = $(window)[0];
         details += '<b>Window</b><br />';
         details += 'innerHeight:' + winElement.innerHeight + '<br />';
         details += 'outerHeight:' + winElement.outerHeight + '<br />';
         details += 'pageYOffset:' + winElement.pageYOffset + '<br />';
         details += 'screenTop:' + winElement.screenTop + '<br />';
         details += 'screenY:' + winElement.screenY + '<br />';
         details += 'scrollY:' + winElement.scrollY + '<br />';

         details += '<b>End of page</b><br />';
         details += 'Test:' + (docElement.scrollHeight - winElement.innerHeight) + '=' + winElement.pageYOffset + '<br />';
         details += 'End of Page? ';
         if ((docElement.scrollHeight - winElement.innerHeight) == winElement.pageYOffset) {
             details += 'YES';
         } else {
             details += 'NO';
         }

         $('#test').html(details);
      });
   });
</script>
<div id="test" style="position: fixed; left:0; top: 0; z-index: 9999; background-color: #FFFFFF;">

11
var elemScrolPosition = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

它计算元素底部到滚动条的距离。如果滚动条已经滑动到底部,则为0。


这个有时候不起作用,因为 scrollTop 不是四舍五入的(可能是小数),所以有时候即使滚动到底部,值也不是0。 - trusktr
@trusktr会检查并更新我的答案。 - Vasyl Gutnyk

10

这是我的两分意见

$('#container_element').scroll(function() {
    console.log($(this).scrollTop() + ' + ' + $(this).height() + ' = ' + ($(this).scrollTop() + $(this).height()) + ' _ ' + $(this)[0].scrollHeight);
    if($(this).scrollTop() + $(this).height() == $(this)[0].scrollHeight) {
        console.log('bottom found');
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接