# 懒加载

# 什么是懒加载

在页面一打开时,有些还未被渲染的数据跟着一起加载,会降低渲染速度,影响页面性能。这时就需要将未被渲染的数据暂时不加载出来,等到需要渲染的时刻再加载出来,这便是懒加载

# 应用场景

  1. 一个页面中如果有多张图片,而图片是在页面视图之外的,那么在页面初始渲染时,应暂时不加载图片资源。等图片进入视图区域,再进行加载。
  2. 当后端连续发送了上万条数据时,直接通过数组api迭代是十分损耗性能的一种做法,这时也可以采用懒加载。

# 解决方案

本文主要以图片懒加载为例。

  1. 使用getBoundingClientRect(),获取图片相对于整个屏幕视图的顶部高度,与容器的高度做对比,相等时则加载图片资源。
<script setup>
import { onMounted, reactive, ref } from "vue";
import aImg from "../assets/1.png";
import bImg from "../assets/2.png";
import cImg from "../assets/3.png";
import dImg from "../assets/4.png";
const dataList = reactive({
  imgList: [aImg, bImg, cImg, dImg],
});
const imgRef = ref();

onMounted(() => {
  const containerDom = document.getElementById("million_page");
  const containerDomHeight = containerDom.clientHeight;
  const imgDomArr = document.querySelectorAll("img");

  containerDom.addEventListener("scroll", () => {
    imgDomArr.forEach((image) => {
      const imageTop = image.getBoundingClientRect().top;
      if (imageTop < containerDomHeight) {
        const data_src = image.getAttribute("data-src");
        image.setAttribute("src", data_src);
      }
    });
  });
});
</script>


<template>
  <div id="million_page">
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <img
      v-for="(item, index) in dataList.imgList"
      :key="index"
      :data-src="item"
      ref="imgRef"
    />
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
  </div>
</template>


<style lang="less">
#million_page {
  height: 50vh;
  border: 1px solid black;
  overflow-y: auto;
}
</style>
  1. 使用intersectionObserver(),交叉观察 api。将需要观察的Dom放入它的observe方法。在回调中做逻辑判断。
<script setup>
import { onMounted, reactive, ref } from "vue";
import aImg from "../assets/1.png";
import bImg from "../assets/2.png";
import cImg from "../assets/3.png";
import dImg from "../assets/4.png";
const dataList = reactive({
  imgList: [aImg, bImg, cImg, dImg],
});
const imgRef = ref();

// 回调中的参数,为实体数组,存着每一个被观察到的dom。
const callback = (entries) => {
  entries.forEach((entry) => {
    // 如果他被观察到了(即进入了视图区域内)
    if (entry.isIntersecting) {
      let image = entry.target;
      let data_src = image.getAttribute("data-src");
      image.setAttribute("src", data_src);
    }
  });
};

// 创建交叉观察实例,传入回调。
const observer = new IntersectionObserver(callback);

onMounted(() => {
  const containerDom = document.getElementById("million_page");
  const imgDomArr = document.querySelectorAll("img");
  containerDom.addEventListener("scroll", () => {
    imgDomArr.forEach((image) => {
      observer.observe(image);
    });
  });
});
</script>


<template>
  <div id="million_page">
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <img
      v-for="(item, index) in dataList.imgList"
      :key="index"
      :data-src="item"
      ref="imgRef"
    />
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
    <p>我是p我是p我是p我是p我是p我是p我是p我是p我是p我是p</p>
  </div>
</template>


<style lang="less">
#million_page {
  height: 50vh;
  border: 1px solid black;
  overflow-y: auto;
}
</style>

# 小结

  1. 若是图片懒加载,推荐使用IntersectionObserver方法,它在性能上比getBoundingClientRect好用,不会引起回流。
  2. 若是数据表格懒加载,则依然需要使用到getBoundingClientRect,因为在观察api无法将未形成的数据行dom放入observe方法。