前言
滚动加载和分页在前端其实用的场景挺多的,都是避免一次性请求太多数据导致网站加载数据过慢,这次来做做无限滚动加载,具体滚动加载和分页的区别可以看这篇文章
无限滚动原理
其实无限滚动的原理很简单,用户滚动,当滚动条到底的时候就加载,所以只需要知道滚动条和底部的距离,就可以完成了,当然其中还有一些细节,我们介绍几种方法。
第一种方法
首先,浏览器提供了很多属性,可以给我们计算位置:
scrollHeight
- 读写:只读
- 描述:包括 overflow 样式属性导致的视图中不可见内容,没有垂直滚动条的情况下,scrollHeight 值与元素视图填充所有内容所需要的最小值 clientHeight 相同。包括元素的 padding ,但不包括元素的 margin.
scrollTop
- 读写:可读可写
- 描述:这个Element.scrollTop 属性可以设置或者获取一个元素距离他容器顶部的像素距离。一个元素的 scrollTop 是可以去计算出这个元素距离它容器顶部的可见高度。当一个元素的容器没有产生垂直方向的滚动条,那它的 scrollTop 的值默认为0.
clientHeight
- 读写:只读
- 描述:对于没有定义CSS或者内联布局盒子的元素为0,否则,它是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度 (如果存在)来计算.
使用公式
如果元素滚动到底,下面等式返回 true,没有则返回 false.element.scrollHeight - element.scrollTop === element.clientHeight
局限
这种方法不能确定浏览器的滚动条是否滚到底,也就是说通常我们浏览器右边的滚动条的滚动到底,这个公式没用:1
2
3
4
5
6
7
8function scroll(){
var pageHeight = document.body.scrollHeight
var viewportHeight = document.body.clientHeight;
var scrollHeight = document.body.scrollTop;
console.log(pageHeight - scrollHeight === viewportHeight );
}
setInterval(lowEnough,1000); // 不管滚动条有没有到底都不会变
起作用的一般是 textarea 这些标签内部的滚动条。
具体例子
第二种方法
这种其实也是用到浏览器提供的属性:
offsetHeight
- 读写:只读
- 描述:它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。
window.innerHeight
- 读写:只读
- 描述:浏览器窗口的视口(viewport)高度(以像素为单位),如果存在水平滚动条,则包括它。
document.documentElement
- 读写:只读
- 描述:使用这个只读属性能很方
便的获取到任意文档的根元素。 HTML 文档通常包含一个子节点
,可能在它前面还有个
DOCTYPE声明。XML 文档通常包含多个子节点:根元素,
DOCTYPE声明,和 [processing instructions](https://developer.mozilla.org/zh-CN/docs/DOM/ProcessingInstruction)。 所以你应该使用
document.documentElement `来获取根元素, 而不是document.firstChild
window.pageYOffset
- 读写:只读
- 描述:
pageYOffset
是 scrollY 的别名。
返回文档在垂直方向已滚动的像素值。
具体使用
1 | function lowEnough(){ |
用这个函数就能够监控整个页面的滚动。
第三种方法
看看一个api:
getBoundingClientRect
返回一个 DOMRect
对象,该对象限定了选定的文档对象的内容,该方法返回了一个矩形,这个矩形包围了该文档对象中所有元素的边界矩形集合。
使用场景
在长列表最后埋一个空元素,然后判断这个空元素是否可见,如果可见,则加载,掘金就是用这种方案
最后一个是空元素
使用公式
1 | function checkIsTotalVisible (element) { |
第四种方法
也是和第三种一样,判断元素是否出现来加载,用到一个api:
IntersectionObserverEntry
这里就引用 阮一峰的文章1
var io = new IntersectionObserver(callback, option);
上面代码中,IntersectionObserver
是浏览器原生提供的构造函数,接受两个参数:callback
是可见性变化时的回调函数,option
是配置对象(该参数可选)。
构造函数的返回值是一个观察器实例。实例的 observe
方法可以指定观察哪个 DOM 节点。
1 | // 开始观察 |
上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。
1 | io.observe(elementA); |
callback 参数
目标元素的可见性变化时,就会调用观察器的回调函数callback
。
callback
一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
1 |
|
上面代码中,回调函数采用的是箭头函数的写法。callback
函数的参数(entries
)是一个数组,每个成员都是一个IntersectionObserverEntry
对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries
数组就会有两个成员。
IntersectionObserverEntry 对象
IntersectionObserverEntry
对象提供目标元素的信息,一共有六个属性。
1 |
|
每个属性的含义如下。
time
:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒target
:被观察的目标元素,是一个 DOM 节点对象rootBounds
:根元素的矩形区域的信息,getBoundingClientRect()
方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
boundingClientRect
:目标元素的矩形区域的信息intersectionRect
:目标元素与视口(或根元素)的交叉区域的信息intersectionRatio
:目标元素的可见比例,即intersectionRect
占boundingClientRect
的比例,完全可见时为1
,完全不可见时小于等于0
上图中,灰色的水平方框代表视口,深红色的区域代表四个被观察的目标元素。它们各自的intersectionRatio
图中都已经注明。
实例:无限滚动
无限滚动(infinite scroll)的实现也很简单。
1 |
|
无限滚动时,最好在页面底部有一个页尾栏(又称sentinels)。一旦页尾栏可见,就表示用户到达了页面底部,从而加载新的条目放在页尾栏前面。这样做的好处是,不需要再一次调用observe()
方法,现有的IntersectionObserver
可以保持使用。