Vue 3与IntersectionObserver API:观察DOM元素进入视口

2025-04发布7次浏览

Vue 3 和 IntersectionObserver API 的结合可以实现高效地观察 DOM 元素是否进入视口,从而优化性能和用户体验。本文将深入解析如何在 Vue 3 中使用 IntersectionObserver API,并通过实际代码示例展示其用法。


一、IntersectionObserver API 简介

IntersectionObserver 是一个现代浏览器 API,用于高效地检测目标元素是否与视口或其他祖先元素发生交集(即进入或离开视口)。相比传统的 scrollresize 事件监听器,IntersectionObserver 提供了更高效的解决方案,因为它不需要频繁触发回调函数。

核心概念

  1. 观察目标:需要被观察的 DOM 元素。
  2. 根元素(root):默认为视口,也可以指定其他容器作为参考。
  3. 阈值(threshold):定义目标元素与根元素交集的比例,取值范围为 [0, 1],表示从完全不相交到完全相交的状态。

二、Vue 3 中使用 IntersectionObserver

在 Vue 3 中,我们可以利用 Composition API 或 Options API 来集成 IntersectionObserver。以下分别介绍两种方式的实现。

1. 使用 Composition API

Composition API 提供了更灵活的方式管理状态和逻辑,适合复杂场景。

示例:懒加载图片
<template>
  <div>
    <img v-for="(src, index) in images" :key="index" :src="getSrc(src)" @load="onLoad(index)" />
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const images = ref([
      'https://example.com/image1.jpg',
      'https://example.com/image2.jpg',
      // 更多图片...
    ]);
    const observer = ref(null);
    const loadedImages = ref([]);

    const getSrc = (src) => {
      return loadedImages.value.includes(src) ? src : '';
    };

    const onLoad = (index) => {
      loadedImages.value.push(images.value[index]);
    };

    const observeImage = (el) => {
      if (observer.value) {
        observer.value.observe(el);
      }
    };

    onMounted(() => {
      observer.value = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.value.unobserve(img); // 停止观察
          }
        });
      }, { threshold: 0.1 });

      document.querySelectorAll('img').forEach(observeImage);
    });

    onUnmounted(() => {
      if (observer.value) {
        observer.value.disconnect();
      }
    });

    return { images, getSrc };
  },
};
</script>
解析:
  • 我们使用 IntersectionObserver 监听每个图片元素。
  • 当图片进入视口时,将其 data-src 属性赋值给 src,从而实现懒加载。
  • 在组件卸载时调用 disconnect() 方法清理观察器。

2. 使用 Options API

Options API 更加直观,适合简单场景。

示例:无限滚动
<template>
  <div>
    <ul>
      <li v-for="(item, index) in items" :key="index">{{ item }}</li>
      <div ref="observerTarget">加载更多...</div>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`),
      observer: null,
    };
  },
  mounted() {
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          this.loadMoreItems();
        }
      });
    }, { threshold: 0.5 });

    this.observer.observe(this.$refs.observerTarget);
  },
  beforeDestroy() {
    if (this.obswer) {
      this.observer.disconnect();
    }
  },
  methods: {
    loadMoreItems() {
      const newItemCount = 10;
      const start = this.items.length + 1;
      const newItems = Array.from({ length: newItemCount }, (_, i) => `Item ${start + i}`);
      this.items = [...this.items, ...newItems];
    },
  },
};
</script>
解析:
  • 我们通过 ref 获取需要观察的目标元素。
  • 当目标元素进入视口时,触发 loadMoreItems 方法加载更多数据。
  • 组件销毁时清理观察器以避免内存泄漏。

三、IntersectionObserver 的高级用法

1. 自定义根元素

IntersectionObserver 不仅可以观察视口中的元素,还可以观察某个容器内的子元素。

const options = {
  root: document.querySelector('.custom-container'), // 指定根元素
  rootMargin: '0px', // 边距调整
  threshold: 0.5, // 阈值
};

const observer = new IntersectionObserver(callback, options);

2. 动态更新观察选项

可以通过 unobserve 和重新 observe 来动态调整观察选项。

observer.unobserve(targetElement);
observer.observe(targetElement, updatedOptions);

四、性能优化与注意事项

  1. 避免滥用:IntersectionObserver 虽然高效,但仍然会占用资源,应避免对大量元素同时进行观察。
  2. 兼容性:确保目标浏览器支持 IntersectionObserver,必要时可引入 polyfill。
  3. 清理观察器:在组件销毁或不再需要时调用 disconnect() 方法释放资源。