前言
个人学习的记录,方便后续复习,如果有错误或者更好的方法,希望看到的大佬指出
无限加载
传统的无线加载是监听scroll
事件,通过一些计算,来判断是否已经拉到底部,然后调用接口,刷新列表
intersectionObserver,可以自动观察元素是否可见,用起来也非常简单let observer = new IntersectionObserver(callback, option);
intersectionObserver
参数
有两个参数callback和option
callback
当目标元素的可见性发生变化的时候,就是说看见和消失的时候,都会触发回调
entries
callback也有参数entries
,他是一个数组,数组中的isIntersecting
可以用来判断观察元素是否可见
其实到了这里就可以完成无限滚动了,其他的参数可以参考阮一峰的日志
开始
页尾栏
完成无限滚动,底部最好有一个页尾栏,这里就简单的意思一下,<div class="bottom"></div>
这个就当成页尾栏
<div>
<u-list :items="items"></u-list>
<div class="bottom"></div>
</div>
自定义指令
之前的记录中也说了,为了保证methods中的函数都是纯粹的逻辑,不去处理DOM,可以将需要对DOM进行处理的东西委托给自定义指令处理,所以这里给页尾栏添加一个自定义指令
fetchNext
是调用列表接口的函数,就不展示了
<div class="bottom" v-intersect="{ handler: fetchNext }"></div>
代码
由于比较简单,直接贴上完整代码,注意一下细节就行,
- 观察元素不可见的时候不调用获取列表的函数
- 在inserted钩子函数中观察元素,并且要注意给el一个属性,使其可以在unbind中也获取到observer,方便unbind钩子函数中停止观察
const intersect = {
inserted(el, binding) {
const value = binding.value
const { handler, options = {} } = value;
let observer = new IntersectionObserver((entries = [], options) => {
if (!el._observer) return
if (handler && el._observer.init) {
const isIntersecting = Boolean(
entries.find((entry) => entry.isIntersecting)
)
if (isIntersecting) {
handler()
}
}
el._observer.init = true
})
el._observer = { init: false, observer }
observer.observe(el);
},
unbind(el) {
if (!el._observe) return;
//停止观察
el._observe.observer.unobserve(el);
delete el._observe;
}
}
实际效果演示
黑色的那个DIV就是一个简陋的页尾栏,比较简陋,感兴趣的可以优化一下
长列表优化
但是这个时候会发现一个问题,就是随着列表的长度的增加,DOM节点也会增加(如下图),当DOM节点到达一定数量的时候,会发生卡顿,所以接下来就要对他进行优化了
组件
<u-infinite-list #default="{ sliceItems }" :items="items" :item-height="80">
<u-list :items="sliceItems"></u-list>
</u-infinite-list>
使用UInfiniteList
组件将需要渲染的列表通过slot在父组件中传入UList中去
详解
<div class="x-infinite" ref="container" :style="{ padding: padding }">
<slot :sliceItems="sliceItems"></slot>
</div>
我们的目的就是将不显示的列表把他从list中删除,所以就要知道上/下拉的时候,有多少列表被隐藏了,所以需要知道当前下拉距离和窗口文档显示区的高度,还需要有一个过度的过程
data() {
return {
buffer: 5, //优化用户体验,一个过度的作用
scrollTop: 0, //document.body.scrollTop 网页被卷去的高
viewportHeight: 0, //window.innerHeight返回窗口的文档显示区的高度
};
},
然后通过计算属性获得over(数组的开始索引) ,down(数组的结束索引),sliceItems(需要渲染的数组), padding(div样式用来减少div渲染列表的区域)这些数据
computed: {
over() {
return Math.max(
Math.ceil(this.scrollTop / this.itemHeight) - this.buffer,
0
);
},
down() {
return Math.min(
Math.ceil(
(this.scrollTop + this.viewportHeight) / this.itemHeight + this.buffer![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9348d3494d9748918c329e1093893b44~tplv-k3u1fbpfcp-watermark.image)
),
this.items.length
);
},
sliceItems() {
return this.items.slice(this.over, this.down);
},
padding() {
return `${this.over * this.itemHeight}px 0 ${
Math.max(this.items.length - this.down) * this.itemHeight
}px 0`;
},
},
监听浏览器滚动
passive: true
scroll时间的默认行为(页面滚动)是可以被preventDefault()组织的,所以他需要等监听器执行完毕,才能执行默认行为,,如果监听函数执行时间很长的话,就会出现卡顿,设置passive: true
就可以是默认行为和监听函数一起执行
throttle
节流函数,也可以减少对DOM的操作,对DOM进行一定的优化。
created() {
this.scrollTop = document.documentElement.scrollTop;
this.viewportHeight = window.innerHeight;
window.addEventListener("scroll", this.onScroll, {
passive: true,
});
},
destroyed() {
window.removeEventListener("scroll", this.onScroll);
},
methods: {
onScroll: throttle(function () {
this.scrollTop = document.documentElement.scrollTop;
this.viewportHeight = window.innerHeight;
}),
},
结束
到这里就结束了,感兴趣的可以自行试一下效果