How Vue3 loads images elegantly

How Vue3 loads images elegantly

Recently, a function was developed. The homepage of the page will load a large number of pictures. When entering the page for the first time, the performance of the page will be reduced.

Ever since, I improved this function so that all images can be loaded automatically and lazily.

principle

The main underlying logic of this function is IntersectionObserver APIto IntersectionObserverobserve the visibility and position changes of elements in the browser. It can help developers implement some dynamic behaviors, such as lazy loading of images, infinite scrolling, etc.

A simple example is as follows:

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

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

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

practice

Next we implement a general hook, basic function as follows:

  1. Provide a default placeholder image for the image srcand provide srcattributes
  2. Pass in the attributes corresponding to the image ref.
  3. When the picture enters the visible area, use srcthe attribute to replace srcthe attribute
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("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]));
  });
};
<script setup lang="ts">
import { ref } from "vue";
import { useInView } from "./hooks/useInView";

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

<template>
  <div
    v-for="(_, idx) in new Array(200).fill(11)"
  >
    <img
      ref="imgRef"
      src="https://via.placeholder.com/200"
      :src="`https://picsum.photos/200/${180 + idx}`"
      alt="b"
    />
  </div>
</template>

The actual effect is as follows

Insert image description here

Although the basic functional requirements have been completed, it is not elegant enough yet! ! !

optimization

Next, we add a transition animation. Each time when the image is loaded, it transitions from the placeholder image to the normal image mode.

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

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

Insert image description here

The complete code is as follows:

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("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]));
  });
};
<script setup lang="ts">
import { ref } from "vue";
import { useInView } from "./hooks/useInView";

const imgRef = ref(null);

useInView(imgRef);

</script>

<template>
  <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"
      :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>

Guess you like

Origin blog.csdn.net/dfc_dfc/article/details/133915126