介绍
懒加载(Lazy Loading)是一种常见的前端优化技术,用于延迟加载页面上的资源(如图片、视频、组件等),直到它们真正需要被显示时才加载。这种技术可以显著提高页面的加载速度,减少初始加载时的资源消耗,提升用户体验。
原理
懒加载的核心思想是:只加载用户当前需要的内容。例如,当用户滚动到页面的某个部分时,才加载该部分的图片或组件,而不是在页面加载时一次性加载所有内容。
展示
- 我们可以在控制台看到一旦底部红色容器进入用户的窗口,那么就会触发懒加载机制,这里我设置了触发一次加载4个数据
详细解析
模版部分
- 这里我应用了
Element-Plus
组件库中的Card
组件。 <h2>vue懒加载</h2>
:页面标题。<el-card>
:使用 Element Plus 的 el-card 组件来显示数据。v-for
指令用于循环渲染 visibleResult 中的每一项数据。:key="item.title"
:为每个 el-card 设置唯一的 key,通常使用数据的唯一标识符(如 id 或 title)。
<template #header>:定义卡片的头部,显示 item.title。<p>
:显示 item.description。<div ref="obserRef" style="height: 100px; width: 100px; background: red"></div>
:这是一个占位符元素,用于触发懒加载逻辑。当这个元素进入视口时,会触发加载更多数据的操作。
<template>
<div id="app-lazyload">
<h2>vue懒加载</h2>
<el-card
v-for="item in visibleResult"
:key="item.title"
style="width: 500px; margin-top: 30px"
>
<template #header>
<div class="标题">
<span>{{ item.title }}</span>
</div>
</template>
<p>
{{ item.description }}
</p>
<template #footer></template>
</el-card>
<div
ref="obserRef"
style="height: 100px; width: 100px; background: red"
></div>
</div>
</template>
脚本部分
数据和变量
data
:从 …/data/data.json 导入的初始数据。result
:存储所有数据的响应式引用。obserRef
:通过 ref 创建的响应式引用,指向页面中的占位符元素。visibleResult
:存储当前已加载并显示在页面上的数据。
IntersectionObserver
observer
:创建一个 IntersectionObserver
实例,用于监听占位符元素是否进入视口。
entries.forEach((entry) => { ... })
:遍历所有被观察的元素。
entry.isIntersecting
:判断当前元素是否进入视口。
visibleResult.value.length < result.value.length
:确保还有更多数据可以加载。
loadMoreData()
:调用加载更多数据的函数。
加载更多数据
loadMoreData
:从 result 中获取更多数据并添加到 visibleResult 中。
result.value.slice(visibleResult.value.length, visibleResult.value.length + 4)
:每次加载 4 条数据。
visibleResult.value.push(...newData
):将新数据添加到 visibleResult 中。
生命周期钩子
onMounted
:
loadMoreData():组件挂载时,先加载一部分数据。
observer.observe(obserRef.value):开始监听占位符元素。
onUnmounted
:
observer.disconnect():组件卸载时,停止监听占位符元素,避免内存泄漏。
import data from "../data/data.json";
import { ref, onMounted, onUnmounted } from "vue";
interface Data {
title: string;
dispriation: string;
}
const result = ref<Data[]>(data);
//监听容器DOM
const obserRef = ref(null);
//窗口内数据
const visibleResult = ref<Data[]>([]);
//懒加载逻辑
const observer = new IntersectionObserver((entires) => {
entires.forEach((entry) => {
if (
entry.isIntersecting &&
visibleResult.value.length < result.value.length
) {
//触发容器进入视图
//获取数据
loadMoreData();
}
});
});
//加载更多数据
const loadMoreData = () => {
console.log("懒加载触发了");
//有数据加载,一回加载3个数据
const newData = result.value.slice(
visibleResult.value.length,
visibleResult.value.length + 4
);
visibleResult.value.push(...newData);
};
//初始化加载
onMounted(() => {
loadMoreData();
//监听容器
observer.observe(obserRef.value);
});
//卸载observer
onUnmounted(() => {
observer.disconnect();
});
代码
Mock数据
[
{
"title": "熊出没",
"description": "保护森林,熊熊有责"
},
{
"title": "小马宝莉",
"description": "奇幻冒险,传递友谊与勇气"
},
{
"title": "巴巴爸爸",
"description": "温馨家庭,奇妙变形冒险"
},
{
"title": "虹猫蓝兔",
"description": "武侠情怀,传递正义勇敢"
},
{
"title": "花园宝宝",
"description": "奇幻花园,启蒙认知与想象"
},
{
"title": "天线宝宝",
"description": "欢乐互动,陪伴幼儿成长"
},
{
"title": "米老鼠和唐老鸭",
"description": "经典形象,带来欢乐时光"
},
{
"title": "哆啦A梦",
"description": "神奇道具,开启奇幻之旅"
},
{
"title": "奥特曼",
"description": "光之巨人,守护地球和平"
},
{
"title": "成龙历险记",
"description": "环球冒险,收集神秘符咒"
},
{
"title": "成龙历险记之魔法 Cookbook",
"description": "美食魔法,开启奇幻旅程"
},
{
"title": "海底小纵队",
"description": "探索海底,保护海洋生物"
},
{
"title": "大头儿子小头爸爸",
"description": "温馨家庭,快乐成长"
},
{
"title": "喜羊羊与灰太狼",
"description": "斗智斗勇,欢乐不断"
},
{
"title": "蓝猫龙骑团",
"description": "勇敢冒险,守护和平"
},
{
"title": "猫和老鼠",
"description": "追逐打闹,欢乐无穷"
},
{
"title": "黑猫警长",
"description": "惩恶扬善,守护城市安全"
},
{
"title": "熊熊乐园",
"description": "寓教于乐,陪伴快乐童年"
},
{
"title": "超级飞侠",
"description": "环游世界,传递爱心与勇气"
},
{
"title": "小猪佩奇",
"description": "温馨日常,分享家庭欢乐"
},
{
"title": "大耳朵图图",
"description": "成长故事,充满童真童趣"
},
{
"title": "开心宝贝",
"description": "机智勇敢,守护地球和平"
},
{
"title": "喜羊羊与灰太狼之勇闯四季城",
"description": "四季冒险,展现智慧与力量"
},
{
"title": "海绵宝宝",
"description": "海底奇遇,带来无尽欢笑"
},
{
"title": "名侦探柯南",
"description": "解谜破案,维护正义"
},
{
"title": "火影忍者",
"description": "忍道精神,追逐梦想"
}
]
前端代码
- 这里为
.vue
文件
<template>
<div id="app-lazyload">
<h2>vue懒加载</h2>
<el-card
v-for="item in visibleResult"
:key="item.title"
style="width: 500px; margin-top: 30px"
>
<template #header>
<div class="标题">
<span>{{ item.title }}</span>
</div>
</template>
<p>
{{ item.description }}
</p>
<template #footer></template>
</el-card>
<div
ref="obserRef"
style="height: 100px; width: 100px; background: red"
></div>
</div>
</template>
<script setup lang="ts">
import data from "../data/data.json";
import { ref, onMounted, onUnmounted } from "vue";
interface Data {
title: string;
dispriation: string;
}
const result = ref<Data[]>(data);
//监听容器DOM
const obserRef = ref(null);
//窗口内数据
const visibleResult = ref<Data[]>([]);
//懒加载逻辑
const observer = new IntersectionObserver((entires) => {
entires.forEach((entry) => {
if (
entry.isIntersecting &&
visibleResult.value.length < result.value.length
) {
//触发容器进入视图
//获取数据
loadMoreData();
}
});
});
//加载更多数据
const loadMoreData = () => {
console.log("懒加载触发了");
//有数据加载,一回加载3个数据
const newData = result.value.slice(
visibleResult.value.length,
visibleResult.value.length + 4
);
visibleResult.value.push(...newData);
};
//初始化加载
onMounted(() => {
loadMoreData();
//监听容器
observer.observe(obserRef.value);
});
//卸载observer
onUnmounted(() => {
observer.disconnect();
});
</script>
<style scoped>
#app-lazyload {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
</style>