项目中遇到有一个图片列表,需要实时更新获取最新图片,随着滚动加载数据越来越多时,需要不停的发送大量的接口去查询图片信息。
此时可以使用IntersectionObserver来观察元素与视窗的交叉情况,在元素进入可视范围内,再去加载先关逻辑。这样可以优化页面加载首屏渲染的速度和滚动的性能
通过分装vue指令实现可视区域的加载
// observeVisibility.js
/**
* @param {*} el 指定dom
* @param {*} binding 绑定对象
* parent:父级选择器
* callback:在可视区域内的回调函数
*/
let observeVisibility = {}
observeVisibility.install = function (Vue) {
Vue.directive('observe-visibility', {
inserted (el, binding) {
const options = binding.value || {}
const parentSelector = options.parent || '' // 父级选择器
const callback = options.callback || (() => {}) // 在可视区域内的回调函数
const parentEl = parentSelector
? el.closest(parentSelector)
: el.parentElement
if (!parentEl) {
console.warn(`Parent element not found for selector: ${parentSelector}`)
return
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) { // el与parentEl是否交叉
callback(true) //可视区域内
} else {
callback(false) //被遮住隐藏了
}
})
}, { root: parentEl, threshold: 0.1 })
observer.observe(el)
el._observer = observer
el._parentEl = parentEl
},
unbind (el, binding) {
const options = binding.value || {}
const callback = options.callback || (() => {}) // 在可视区域内的回调函数
callback(false)
if (el._observer) {
el._observer.disconnect()
delete el._observer
}
if (el._parentEl) {
delete el._parentEl
}
}
})
}
export default observeVisibility
main.js中引用:
//main.js
import observeVisibility from '@/directive/observeVisibility'
Vue.use(observeVisibility)
组件中使用:
<template>
<div id="app">
<div class="parent-container">
<div class="item left"
v-observe-visibility="{
parent: '.parent-container',
callback: (type) => fetchImageUrl(type,item.id)
}"
v-for="item in list"
:key="item.id">
<div class="item-content">
<div class="title">{{item.imgName}}</div>
<img :src="imageUrl[item.id]"
alt="">
</div>
</div>
</div>
</template>
export default {
name: 'App',
data () {
return {
list: [],
imgIds: [],
}
},
async created () {
await this.getListFn()
setInterval(() => {
if (!this.imgIds.length) return
this.getImgUrl(this.imgIds) // 可是区域的ids集合
}, 1000)
},
methods: {
handleImageIds (type, id) {
const idx = this.imgIds.indexOf(id)
if (type === true) { // 进入可视区域
idx == -1 && this.imgIds.push(id)
} else { // 超出可视区域
idx != -1 && this.imgIds.splice(idx, 1)
}
},
async getListFn () {
// fetch data from server
// ....
this.list = []
},
getImgUrl (ids) {
// fetch image url from server
// ....
}
}
}
<style lang="less">
.parent-container {
height: 80vh;
overflow-y: auto;
}
</style>