uselntersectionObserver实现数据懒加载

151 阅读2分钟

1.应用场景

如果访问的页面数据过多,为减少并发量,减少系统资源的消耗,这时候就需要用到懒加载,实现当组件进入可视区后再去发请求加载数据。

2.思路分析

如何判断一个dom元素是否进入了可见区域?

传统:获取dom位置 → getBoundingClientRect()

现在:直接判断元素进入可视区域的比例 → IntersectionObserver()

3.使用

① 定义一个target,并导出,监测谁,就在谁身上通过ref调用。

② 引入 useIntersectionObserver 并使用

  • target 是观察的目标容器 dom对象 | 组件对象 
  • stop 是一个函数。如果调用它,就会停止观察(是否进入或移出可视区域的行为)
  • isIntersecting 是一个bool值,表示是否进入可视区域。 true表示 进入 false表示 移出
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
  setup () {
     const target = ref(null)
     const { stop } = useIntersectionObserver(target, ([{ isIntersecting }], observerDom) => {
       console.log('home-hot', isIntersecting, stop)
       // 如果target可见
       if (isIntersecting) {
         // 1.停止观察
         stop()
         // 2.发ajax
         findHot().then(data => {
           goods.value = data.result
         })
       }
     })
 
    return { target }
  }
}

4.封装通用逻辑

4.1 原因

在项目中,如果多个组件都需要用到懒加载,这时候每次都在组件中引入使用,会产生大量重复代码。

import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
 
/**
 * 功能: 数据懒加载
 *
 * @param {*} fn  当目标可见时,要调用一次的函数
 * @returns target: 要观察的目标(vue3的引用)
 */
const useLazyData = (fn) => {
  const target = ref(null)
  const { stop } = useIntersectionObserver(
    target, // target 是vue的对象引用。是观察的目标
    // isIntersecting 是否进入可视区域,true是进入 false是移出
    // observerElement 被观察的dom
    ([{ isIntersecting }], observerElement) => {
      if (isIntersecting) { // 目标可见,
        // 1. ajax可以发了,后续不需要观察了
        stop()
        // 2. 执行函数
        fn()
      }
    }
  )
 
  return target
}
 
export default useLazyData

4.2 在组件中使用

import { findNew } from '@/api/home'
import useLazyData from '@/compositions'
export default {
  name: 'HomeNew',
  setup () {
    const fn = () => {
      findNew().then(data => {
        console.log('findNew', data)
        list.value = data.result
      })
    }
    const target = useLazyLoad(fn)
 
    return { target }
  }
}

5滚动加载的bug

如果需要懒加载的组件面积比较大,页面需要滚动比较多的面积才回去加载数据,用户体验不好。

5.1 原因

useIntersectionObserver 默认以进入视口的面积占总元素面积的0.1为基准判断是否进入,而这里的默认比例0.1在元素比较大的时候,会有这个问题:看起来已经可见了,但是我们回调没有执行。

5.2 解决

补充一个参数:threshold ,容器和可视区交叉的占比(进入的面积/容器完整面积) 取值,0-1 之间,默认是0.1,所以需要滚动较多才能触发进入可视区域事件。

  const { stop } = useIntersectionObserver(
    container,
    ([{ isIntersecting }], dom) => {
      if (isIntersecting) {
        stop()
        fn()
      }
      }, {
       threshold: 0
      }
  )