性能分析
当页面中包含大量元素和复杂布局的时候,页面会有明显的卡顿感,会严重影响用户体验
常见场景:大数据量的列表渲染,如 无限滚动的列表 或 表格
卡顿根源:一次性渲染的 DOM 元素太多,导致性能开销较大
优化方案
监听已渲染 DOM 列表的尾部节点,在其进入视口的时候渲染下一页的数据
判断某个元素是否进入了视口
-
老式解决方案
const { top, right, bottom, left } = Element.getBoundingClientRect()通过
getBoundingClientRect()获取目标元素对应于浏览器视窗的位置,再通过scroll事件进行监听 -
新式解决方案
通过
IntersectionObserver API自动观察元素是否可见,即 目标元素与视口产生一个交叉区就视为可见
掌握 Intersection Observer 观察器
-
API 使用
// 创建观察器 const io = new IntersectionObserver(callback, option) // 开始观察 DOM 节点元素 io.observe(element) // 停止观察 io.unobserve(element) // 关闭观察器 io.disconnect() -
callback回调函数
const io = new IntersectionObserver(entries => { entries.forEach(entry => { // 目标元素的区域信息,也就是 getBoundingClientRect() 的返回值 console.log(entry.boundingClientRect) // 目标元素的可见比率 console.log(entry.intersectionRatio) // 目标元素与根元素交叉的区域信息 console.log(entry.intersectionRect) // 目标元素是否进入可视区域 console.log(entry.isIntersecting) // 根元素的矩形区域信息 console.log(entry.rootBounds) // 被观察的目标,是一个 DOM 节点元素 console.log(entry.target) // 可见性发生变化的时间,相交发生时距离页面打开时的毫秒数,精度为微秒 console.log(entry.time) }) })callback回调函数一般会被触发两次,一次是目标元素刚进入视口,另一次是完全离开视口 -
option配置对象
const io = new IntersectionObserver(entries => { console.log(entries) }, { // 设置什么时候触发回调函数,默认值为 [0],即交叉比例 intersectionRatio 为 0 时触发回调函数 threshold: [0, 0.25, 0.5, 0.75, 1], // 指定目标元素所在的容器节点,也就是滚动容器 root: document.querySelector('#scrollContainer'), // 定义滚动容器的 margin 值 rootMargin: 10px 10px 10px 10px, }) -
注意事项
IntersectionObserver API是异步的,不随着目标元素的滚动同步触发 -
兼容性概览
分页加载 + 增量渲染
-
模板结构
<ul class="container" ref="scrollContainer"> <li v-for="(item, index) of records" :key="index">{{ item.name }}</li> <template v-if="records.length !== total"> <li ref="tailItem" v-loading="true" element-loading-text="拼命加载中"></li> </template> <template v-else> <li v-if="total !== 0">没有更多了</li> </template> </ul> -
初始化设置
async created() { this.getRecords() // 获取第一页数据 this.$nextTick(() => { const { scrollContainer, tailItem } = this.$refs this.nodeObserver(scrollContainer, tailItem) // 开启观察器 } } -
获取分页数据
async getRecords() { const { total, data } = await getData(this.searchParams) this.total = total this.records.push(...data) } -
观察尾项实现数据分页加载
nodeObserver(scrollContainer, tailItem) { const io = new IntersectionObserver(entries => { entries.forEach(({ isIntersecting, target }) => { if (isIntersecting && target === node) { this.searchParams.page += 1 this.getRecords() } }) }, { root: scrollContainer }) io.observe(tailItem) }一起学习,加群交流看 沸点