Vue 图片懒加载实现 - 使用 IntersectionObserver API

发布时间:2023-09-02

统计字数:923 字

阅读时间:5 min read

目的

今天继续优化一下博客,首先虽然说我的博文中图片数量比较少,但是如果有个别一两个博文图片数量多,并且图片的大小比较大,那么对用户的流量来说不太友好,那么今天主要是给博文的图片加上懒加载功能。

懒加载也就是以下几个方面:

  1. 当用户浏览窗口看不到图片的时候,图片不进行加载
  2. 当用户能够浏览到图片时再进行加载

这样做有几个优点:

  1. 减少用户加载的流量
  2. 优化页面加载速度

过程

首先要实现该功能就需要实时地监听用户的浏览窗口,也就是当用户的可视区域有图片的时候,图片才进行加载。那么就需要用到一个极其重要的原生 api:IntersectionObserver

IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。

当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

MDN文档的地址: https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

那么实现起来就超级简单了,使用该 api 对图片元素进行监听,当图片进入可视区域的时候再对图片的 src 属性进行赋值,那么浏览器就会自动进行加载了。

同时我的博客使用到了 vueuse 的框架,在 vueuse 中同样提供了该 api 的工具: useIntersectionObserver

这里附上 vueuse 文档的地址: https://vueuse.org/core/useIntersectionObserver/#useintersectionobserver

那么我在代码里面是这样实现的:

vue
<template>
<img
     class="cursor-pointer"
     @click="preview"
     ref="imgRef"
     :src="srcComputed"
     :alt="alt"
     :width="width"
     :height="height"
     />
</template>

<script setup lang="ts">
  import { withTrailingSlash, withLeadingSlash, joinURL } from "ufo";
  import { useRuntimeConfig, computed } from "#imports";
  import loading from "~/assets/svg/loading.svg";

  const props = defineProps({
    src: {
      type: String,
      default: "",
    },
    alt: {
      type: String,
      default: "",
    },
    width: {
      type: [String, Number],
      default: undefined,
    },
    height: {
      type: [String, Number],
      default: undefined,
    },
  });

  const imgRef = ref();
  const isVisible = ref(false);

  const { stop } = useIntersectionObserver(imgRef, ([{ isIntersecting }]) => {
    if (isIntersecting) {
      stop();
      isVisible.value = true;
    }
  });

  const refinedSrc = computed(() => {
    if (props.src?.startsWith("/") && !props.src.startsWith("//")) {
      const _base = withLeadingSlash(
        withTrailingSlash(useRuntimeConfig().app.baseURL),
      );
      if (_base !== "/" && !props.src.startsWith(_base)) {
        return joinURL(_base, props.src);
      }
    }
    return props.src;
  });

  const srcComputed = computed(() => {
    return isVisible.value ? refinedSrc.value : loading;
  });

  const preview = () => {
    window.open(refinedSrc.value, "_blank");
  };
</script>

首先该组件的路径是 /components/content/ProseImg.vue,主要是为了替换掉 Nuxt/Content 的原生组件,以达到自定义MDC的目的。那么主要实现懒加载的代码:

ts
const { stop } = useIntersectionObserver(imgRef, ([{ isIntersecting }]) => {
  if (isIntersecting) {
    stop();
    isVisible.value = true;
  }
});

这部分的用法跟 vueuse 文档都差不多,当进入可视区域的时候先调用 stop 方法,也就是后续不再对该元素进行监听,然后响应式修改 isVisible 的值,通过 srcComputed 这个 computed 方法来动态赋值图片的 src 属性。

效果

20230902_115816

可以看到在下滑的过程中,图片是懒加载的,当出现在可视区域中才会进行请求。

总结

懒加载是前端开发中常用的开发手段,其中又分为图片懒加载,数据懒加载和组件懒加载。通过懒加载可以有效提高我们页面的性能,优化流量和提高用户体验。

通过 IntersectionObserver 可以很轻松地做到这些功能,同时附上该 api 的兼容图。

image-20230902120539000