什么叫懒加载
懒加载也叫延迟加载,是一种根据用户的活动和导航模式来将某些资源的加载推迟到用户需要时的策略。通常来说,这些资源只有在滚动到可视区域时才会被加载。
如果正确实现,这种资源加载的延迟对于用户体验是无缝的,并且可能有助于改善初始加载性能,包括可交互时间,因为页面开始工作所需的资源更少
为什么需要懒加载
比如在购物app中,都是长页面,没有分页,首次打开将上万种商品的图片全部加载出来是不现实的,这种做法导致用户等待时间长、体验差;也会导致页面渲染卡死;服务器的压力也会很大。所以就需要用懒加载来优化性能
懒加载的原理
以图片懒加载为例
1.将 img 的src属性设置成一张默认图片,在图片加载出来之前显示默认图片。
2.用data-自定义属性名 (data-src) 接收图片的真实路径,待图片滚动到可视区域的时候开始加载图片,待图片加载出来之后替换掉之前的默认图片
具体实现
方法一:
监听滚动事件,通过**getBoundingClientRect()**获取到元素的信息,判断元素的顶点位置有没有超过视口的高度。超过高度则不在视口内,不超过则在视口内。
picsum.photos 是一个随机获取图片的网站,用网络资源展示效果好一点(随机生成图片的时候有的随机生成图片的时候有的random的序号是没有图片的看浏览器控制台请求效果既可)。
<template>
<div class="observer-container">
<div class="img-content">
<div class="img-box" v-for="i in 40" :key="i">
// data-src保存随机获取到的网络资源路径
// src中保存的是一张本地资源图片
// data-index = -1是防止多次加载的
<img
class="img"
src="@/assets/images/randomImg.jpg"
:data-src="`https://picsum.photos/500/500/?random=${Math.floor(Math.random() * 1000)}`"
:data-index="-1"
:data-flag="true"
alt=""
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
onMounted(() => {
watchImg() // 方案一
})
//在离开页面时移除监听器
onUnmounted(window.removeEventListener('scroll', (e) => {}))
// 方案一:通过监听scroll实现
const watchImg = () => {
const imgList = document.querySelectorAll('.img')
window.addEventListener('scroll', (e) => {
imgList.forEach((item, index) => {
let rect = item.getBoundingClientRect()
//判断是否在视口内 ,如果在则替换src,并将data-index属性值设为 当前的索引,防止再次交互时再次加载
if (rect.top < document.body.clientHeight) {
item.dataset.index == -1 && (item.src = item.dataset.src)
item.dataset.index = index
}
})
})
}
</script>
<style lang="scss" scoped>
.observer-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.img-content {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 20px;
.img-box {
width: 500px;
height: 500px;
border: 1px solid #ccc;
border-radius: 10px;
img {
border-radius: 10px;
}
}
}
}
</style>
打开控制台可以看到出现在视口的时候才会请求资源。
方法二:
使用 IntersectionObserver() API 创建一个新的 IntersectionObserver 对象,当其监听到目标元素的可见部分(的比例)超过了一个或多个阈值(threshold)时,会执行指定的回调函数。可以在回调函数中满足我们的需求 IntersectionObserver 有几个常用属性:
root:测试交叉时,用作边界盒元素,当未传入值或者值为null时,则默认使用顶级文档的视口。
rootMargin: 计算交叉时添加到根边界盒的矩形偏移量,可以缩小或扩大根的判定范围从而满足计算需求。默认值为 0px 0px 0px 0px。
threshold:一个包含阈值的列表,按升序排列,列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知(Notification)。
<template>
<div class="observer-container">
<div class="img-content">
<div class="img-box" v-for="i in 40" :key="i">
<img
class="img"
src="@/assets/images/randomImg.jpg"
:data-src="`https://picsum.photos/500/500/?random=${Math.floor(Math.random() * 1000)}`"
:data-index="-1"
:data-flag="true"
alt=""
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
onMounted(() => {
// watchImg() // 方案一
watchImg2() // 方案二
})
// 方案二:通过IntersectionObserver实现
const watchImg2 = () => {
const options = {
root: null,
rootMargin: '0px 0px 0px 0px',
threshold: 0.5,
}
let observer = new IntersectionObserver((entries, observer) => {
for (const entrie of entries) {
if (entrie.isIntersecting) {
// 进入此分支,说明当前的图片和根元素产生了交叉
const img = entrie.target
img.src = img.dataset.src
observer.unobserve(img)
}
}
}, options as any)
// 先拿到所有的图片元素
const imgs = document.querySelectorAll('.img')
imgs.forEach((img) => {
//观察所有的图片元素
observer.observe(img)
})
}
</script>
<style lang="scss" scoped>
.observer-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.img-content {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 20px;
.img-box {
width: 500px;
height: 500px;
border: 1px solid #ccc;
border-radius: 10px;
img {
border-radius: 10px;
}
}
}
}
</style>
个人比较倾向于第二种方案,因为频繁的监听会造成资源泄露、页面卡顿。当然也还有可以优化的地方,比如防抖和节流,这里主要展示懒加载的效果。