IntersectionObserver(交叉口观察员API)结合vue3.x的介绍使用

1,186 阅读3分钟

1 IntersectionObserver

IntersectionObserver API接口 俗称 交叉口观察员,它提供了一种异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法,而祖先元素与窗口( viewport ) 被称为根(root)

该 API 方法的调用:

var observer = new IntersectionObserver(callback[, options]);

// 调用
observer.observer(DOM)

支持两个产生:

  1. callback:是当被监听元素的可见性变化时,触发的回调函数
  2. options:是一个配置参数,可选,有默认的属性值 详情参考

1.1 callback参数

当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数

entries:一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差,callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例。描述了目标元素与root的交叉状态

observer:被调用的IntersectionObserver实例

它包含该元素的一些基本信息和变化的信息如下代码:

var observer = new IntersectionObserver(changes => {
    for (const change of changes) {
        console.log(change.boundingClientRect);
        console.log(change.intersectionRect);
        console.log(change.intersectionRatio);
        console.log(change.isIntersecting);
        console.log(change.rootBounds);
        console.log(change.target);
        console.log(change.time);
    }
}, {})

Snipaste_2022-06-22_21-18-17.jpg 详情参考

其中三个参数是用来获取位置信息的,rootBounds(root元素的位置信息),boundingClientRect(target元素的位置信息),intersectionRect(进入交叉显示区域的位置信息,与intersectionRatio属性有关)

1.2 options的参数

可以用来配置 observer 实例的对象。如果options未指定,observer 实例默认使用文档视口作为 root,并且没有 margin,阈值为 0%(意味着即使一像素的改变都会触发回调函数)。你可以指定以下配置:

那么接下来看看有哪些默认的属性和方法

root: 监听元素的祖先元素Element对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见

rootMargin: 一个在计算交叉值时添加至根的边界盒 (bounding_box (en-US)) 中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的margin 属性等同; 可以参考 The root element and root margin in Intersection Observer API来深入了解 margin 的工作原理及其语法。默认值是"0px 0px 0px 0px"

threshold: 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为 1.0,则意味着整个元素都在可见范围内时才算可见。阈值的默认值为 0.0

1.3 IntersectionObserver 方法

Snipaste_2022-06-22_21-20-21.jpg

2 IntersectionObserver 应用

  1. 图片懒加载
  2. 无限滚动加载
  3. 埋点曝光

2.1 结合 vue3.x 的无限滚动加载组件

首先创建一个观察器,当他与根元素(root)相交的时候,触发回调

<template>
  <div class="target" ref="target">
    <div class="loading" v-if="loading">
      <span class="text">加载中...</span>
    </div>
    <div class="none" v-if="finished">
      <span class="text">亲,没有完了</span>
    </div>
  </div>
</template>
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default {
  name: 'InfiniteLoading',
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    finished: {
      type: Boolean,
      default: false
    }
  },
  setup(props, { emit }) {
    const target = ref(null)

    onMounted(() => {
      // 构建观察器
      const observer = new IntersectionObserver(
        ([{ isIntersecting }]) => {
          // 目标元素与根元素相交
          if (isIntersecting) {
            // console.log('进入可视区了')
            // 触发加载条件:请求加载完成,数据加载完毕
            if (!props.loading && !props.finished) {
              emit('infinite')
            }
          }
        },
        {
          threshold: 0.01 // 相交距离
        }
      )
      // 观察目标元素
      observer.observe(target.value)
    })

    // 组件销毁前停止监听
    onBeforeUnmount(() => {
      observer.disconnect()
    })

    return { target }
  }
}

首先,在 setup 中使用 ref 创建一个响应式变量:const target = ref(null) ; 并且 setup 返回这个变量:return { target } 然后,直接通过 ref="target" 绑定元素为 ref target 最后在 setup 内部,通过 target.value 访问ref

组件销毁前,需要停止监听,所以在生命周期 onBeforeUnmount 里调用观察器的 disconnect 方法

使用:

将该观察器组件引入放在需要加载列表的底部部。将事件处理参数( loading 与 finished ),通过根元素选择器传给子组件,且子组件返回一个 infinite 事件

<InfiniteLoading :finished="finished" :loading="loading" @infinite="传值"/>


setup() {
    const loading = ref(false) // 加载中
    const finished = ref(false) // 是否加载结束
    // 自定义数据逻辑处理
    // ...
    
    return { loading, finished }
  }