无限滚动
![](/images/%E7%BC%96%E7%A8%8B/%E5%AE%9E%E8%B7%B5%E4%B8%8E%E6%8E%A2%E7%A9%B6/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91/bg.webp)
无限滚动
MGRoid原生JS实现无限滚动
无限滚动又称触底加载,它大致意思是滚到到底部时获得新数据,再进行加载展示。
在前端开发的过程中,这个问题100%的开发人员都会碰到,所以就讲一下我是如何处理的。
方法一:监听DOM元素并实时计算
1. 基础原理
从原生JS的层面上来讲,基本原理如下:
- 获取带有滚动条
div
的DOM元素:用于限制可视范围,并生成滚动条获取其中滚动条距离顶端距离,以及获取可视范围高度,还有获取内容总高度,再监听其滚动事件。 - 触发滚动时计算:滚动条距离顶端距离+可视范围高度>=内容总高度-提前触发距离
2. 代码实现
1 | <template> |
3. 完善与思考
因为使用Vue
进行项目编写比较方便,它可以快速实现功能以及路由跳转,所以我就用它写代码了,不过写完后其实还有几个问题:
- 一直快速往下滚动会频繁的触发操作,应该做节流处理。
- 在代码中的响应式数据其实是通过Vue实现的,有空时我将使用原生JS写一个,这也会设计到Vue响应式的本质。
- 我为什么会在
onMounted
中进行BOM绑定
和监听
,这是因为若写在setup()
中时,DOM元素还不存在,此时则无法绑定上。而通过onMounted
(组件加载后),进行绑定就能绑定上。 - 其实一直在监听并计算是会消耗许多内存,所以应该用
IntersectionObserver
这个原生的API实现即可,用户不会关注到代码实现,他们只会在乎实际体验。
方法二:通过IntersectionObserver
(交叉观察器)接口
1. 基础原理
交叉观察器的原理:
- 创建观察器实例:创建一个
IntersectionObserver
实例,并指定一个回调函数。 - 选择目标元素:选择要观察的目标元素。
- 开始观察:通过调用
IntersectionObserver
实例的observe
方法,开始观察目标元素。浏览器会异步地监视目标元素与其祖先元素或视窗之间的交叉状态,直至监视停止。 - 触发回调函数:当目标元素的交叉状态发生变化时,会触发指定的回调函数,并传入一个包含相关信息的数组(entries)。每个entry对象都包含了目标元素的交叉状态信息,如isIntersecting(表示元素是否当前可见)、intersectionRatio(表示目标元素与视口或指定祖先元素的交叉比例)等。
2. 代码实现
1 | <template> |
3. 完善与思考
通过交叉观察器的方法来设置无限滚动是最好的方法,即可以设置加载显示状态,也减少了性能消耗。
想法扩展:
- 用它实现懒加载的效果应该挺不错的。
- 可以确认图片是否展示在了视口上。
- 记录目标元素的展示范围,并执行对于的方法,例如动画显示效果改变、加载定时器,等等方法。
问题:
- 我想如下场景 能不能实现:
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
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intersection Observer with Sticky Root Element</title>
<style>
.scroll_container {
height: 300px;
overflow-y: scroll;
border: 1px solid black;
}
#root {
position: sticky;
height: 100px;
top: 50px;
background-color: rgba(173, 216, 230, 0.623);
padding: 10px;
border: 2px solid blue;
}
#test_box {
height: 600px;
background-color: lightgreen;
}
#target {
height: 200px;
background-color: lightcoral;
}
</style>
</head>
<body>
<div class="scroll_container" id="scroll_container">
<div id="root">
<h2>我是视口</h2>
</div>
<div id="test_box">
<h2>我是填充盒子</h2>
</div>
<div id="target">
<h2>我是目标盒子</h2>
</div>
<div id="test_box">
<h2>我是填充盒子</h2>
</div>
</div>
<script>
const rootElement = document.getElementById('root');
const targetElement = document.getElementById('target');
const observerOptions = {
root: rootElement, // 将 root 绑定为粘性定位的视口元素
rootMargin: '0px',
threshold: 0.1 // 目标盒子可见 10% 时触发
};
const observerCallback = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('目标盒子与 root 相交了');
} else {
console.log('目标盒子与 root 分离了');
}
});
};
const observer = new IntersectionObserver(observerCallback, observerOptions);
observer.observe(targetElement); // 开始观察目标盒子
</script>
</body>
</html> - 结果不能实现,看来相交并不是人眼所看见的相交,而是目标元素得包含在视口元素中,才能使用,这样子的结果就只是加载后打印
目标盒子与 root 分离了
。 - 如果想实现大概是观察目标换成
scroll_container
,而observerOptions
中的rootMargin
得变成'负的scroll_container上边距离root顶端的px值 0px 负的scroll_container下边距离root底端的px值 0px'
,这样子来模拟看到的视口相交,。 - 顺便吐槽一下,B站上课程中讲无限滚动的实现通常还是方法1,而不是方法2。我看了MND上交叉观察器也不是实验性,按理来说应该是用这个好一点,不过也可能是方法1更接近底层原理吧。