Vue3如何优雅的加载大量图片🚀🚀🚀

文章同步在公众号:萌萌哒草头将军,欢迎关注!

最近开发了一个功能,页面首页会加载大量的图片,初次进入页面时,会导致页面性能下降,

于是乎,我改进了这个功能,可以让所有图片自动懒加载。

🚀 原理

这个功能主要的底层逻辑是是使用IntersectionObserver APIIntersectionObserver用于在浏览器中观察元素的可见性和位置变化。它可以帮助开发者实现一些动态行为,如图片的懒加载、无限滚动等。

简单的示例如下:

ts 复制代码
// 创建IntersectionObserver实例
const observer = new IntersectionObserver((entries, observer) => {
  // 遍历观察的元素
  entries.forEach(entry => {
    // 如果元素可见
    if (entry.isIntersecting) {
      // 加载图片
      const img = entry.target;
      const src = img.getAttribute('data-src');
      img.setAttribute('src', src);
      // 停止观察该元素
      observer.unobserve(img);
    }
  });
});

// 获取所有需要懒加载的图片元素
const lazyImages = document.querySelectorAll('.lazy-image');

// 观察每个图片元素
lazyImages.forEach(image => {
  observer.observe(image);
});

🚀 实践

接下来我们实现一个通用的 hook,基本的功能如下:

  1. 给图片提供默认的占位图片 src,同时提供data-src属性
  2. 传入图片对应的 ref 属性。
  3. 当图片进入可视区域时,使用data-src属性替换 src 属性
ts 复制代码
import { onMounted, Ref } from "vue";
const options = {
  // root: document.querySelector(".container"), // 根元素,默认为视口
  rootMargin: "0px", // 根元素的边距
  threshold: 0.5, // 可见性比例阈值
  once: true,
};

function callback(
  entries: IntersectionObserverEntry[],
  observer: IntersectionObserver
) {
  entries.forEach((entry) => {
    // 处理每个目标元素的可见性变化
    if (entry.intersectionRatio <= 0) return;
    const img: Element = entry.target;
    const src = img.getAttribute("data-src");

    img.setAttribute("src", src ?? ""); // 将真实的图片地址赋给 src 属性

    observer.unobserve(img);
  });
}

export const useInView = (ref: Ref) => {
  const observer = new IntersectionObserver(callback, options);

  onMounted(() => {
    Object.keys(ref.value).forEach((e) => observer.observe(ref.value[e]));
  });
};
html 复制代码
<script setup lang="ts">
import { ref } from "vue";
import { useInView } from "./hooks/useInView";

const imgRef = ref(null);
useInView(imgRef);
</script>

<template>
  <h4>公众号:萌萌哒草头将军</h4>
  <div
    v-for="(_, idx) in new Array(200).fill(11)"
  >
    <img
      ref="imgRef"
      src="https://via.placeholder.com/200"
      :data-src="`https://picsum.photos/200/${180 + idx}`"
      alt="b"
    />
  </div>
</template>

实际效果如下

虽然基本的功能要求已经完成了,但是现在还不够优雅!!!

🚀 优化

接下来,我们增加个过渡动画。每次当加载完图片,就从占位图过渡到正常图片模式。

ts 复制代码
img.onload = () => {
  img.setAttribute('class', 'fade-in')
}
css 复制代码
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 应用淡入动画到元素 */
.fade-in {
  animation: fadeIn 0.6s ease-in;
}

完整代码如下:

ts 复制代码
import { onMounted, Ref } from "vue";
const options = {
  // root: document.querySelector(".container"), // 根元素,默认为视口
  rootMargin: "0px", // 根元素的边距
  threshold: 0.5, // 可见性比例阈值
  once: true,
};

function callback(
  entries: IntersectionObserverEntry[],
  observer: IntersectionObserver
) {
  entries.forEach((entry) => {
    if (entry.intersectionRatio <= 0) return;
    const img = entry.target as HTMLImageElement;
    const src = img.getAttribute("data-src");

    img.setAttribute("src", src ?? ""); // 将真实的图片地址赋给 src 属性

    img.onload = () => {
      img.setAttribute("class", "fade-in");
    };

    observer.unobserve(img);
  });
}

export const useInView = (ref: Ref) => {
  const observer = new IntersectionObserver(
    callback,
    options
  );

  onMounted(() => {
    Object.keys(ref.value)
      .forEach((e) => observer.observe(ref.value[e]));
  });
};
html 复制代码
<script setup lang="ts">
import { ref } from "vue";
import { useInView } from "./hooks/useInView";

const imgRef = ref(null);

useInView(imgRef);

</script>

<template>
  <h4>公众号:萌萌哒草头将军</h4>
  <div
    v-for="(_, idx) in new Array(200).fill(11)"
    style="width: 200px height: 200px;"
  >
    <img
      ref="imgRef"
      style="height: 100%"
      src="https://via.placeholder.com/200"
      :data-src="`https://picsum.photos/200/${180 + idx}`"
      alt="b"
    />
  </div>
</template>

<style scoped>
/* 定义淡入动画 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 应用淡入动画到元素 */
.fade-in {
  animation: fadeIn 0.6s ease-in;
}
</style>
相关推荐
恋猫de小郭34 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端