前端工具 - 插件 - vue-lazyload
(本文基于 vue-tiny-lazyload-img (opens new window) 有感而写)
在 Vue3 中实现图片懒加载的原理:
- 使用浏览器提供的
IntersectionObserver
观察元素是否进入可视区域。 - 利用 Vue 自定义指令的功能,为图片添加懒加载功能。
- 使用
newImage.src = lazyImage.dataset.src
将图片缓存在用户本地。 newImage.onload
时,设置lazyImage.src = lazyImage.dataset.src
,从而实现来加载。
# 自定义指令
- 在组件挂载的时候对于元素进行观察。
- 在组件卸载的时候取消对元素的观察。
- 在组件更新的时候,重新对组件进行观察。
**注意:**第三点是很有必要的。例如我们选择更新 img 的 src
使组件进行更新时,
此时需要重新对元素进行观察,以图片不会重新加载。
/**
* Image lazy load using custom directive.
* @param { import('vue').App<Element> } app
*/
export default function use(app) {
if (!app) {
return
}
app.directive('lazy-load', {
mounted(el) {
observe(el)
},
updated(el) {
observe(el)
},
unmounted(el) {
unobserve(el)
},
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# lazy load 的实现
# 状态的改变
首先,我们定义几个状态变换类来标识图片变化过程。
export const V_LAZY_LOADING = 'v-lazy-loading'
export const V_LAZY_LOADED = 'v-lazy-loaded'
export const V_LAZY_ERROR = 'v-lazy-error'
2
3
- 在图片加载完成之前,我们给图片加上一个
v-lazy-loading
的类名。 即<img class="v-lay-loading />
来标识图片正在加载中。 其调用时机在我们设置newImage.src = lazyImg.dataset.src
之前。 - 在图片加载完成的时候,我们给图片加上一个
v-lazy-loaded
的类名, 用于标识图片已经加载完成。其调用时机在newImg.onload
的时候。 - 在图片加载失败的时候,我们给图片加上一个
v-lazy-err
的类名, 用于标识图片已经加载失败。其调用时机在newImg.onerror
的时候。
还有一点需要注意的是**在对 Image 进行观察之前,应该确保当前 Image 元素上
没有 v-lazy-loaded
的类名,这是因为如果组件发生更新的时候,原先已经加载
完成的图片元素可能还携带 v-lazy-loaded
,所以才有了如下的代码:
export function observe(el) {
if (el.classList.contains(V_LAZY_LOADED)) {
el.classList.remove(V_LAZY_LOADED)
}
io.observe(el)
}
2
3
4
5
6
# 核心代码
// lazy-load/v-lazy-load.js
import { V_LAZY_ERROR, V_LAZY_LOADED, V_LAZY_LOADING } from './constant'
const io = getIntersectionObserver()
function getIntersectionObserver() {
return new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const target = entry.target
const src = target.getAttribute('src')
target.classList.add(V_LAZY_LOADING)
const dataSrc = target.getAttribute('data-src') || src
const dataErr = target.getAttribute('data-err') || src
const newImg = new Image()
newImg.src = dataSrc
newImg.onload = () => {
target.src = dataSrc
cleanupAndAdd(target, V_LAZY_LOADED)
}
newImg.onerror = () => {
target.src = dataErr
cleanupAndAdd(target, V_LAZY_ERROR)
}
io.unobserve(target)
}
})
})
}
function cleanupAndAdd(target, className) {
target.removeAttribute('data-src')
target.removeAttribute('data-err')
target.classList.remove(V_LAZY_LOADING)
target.classList.add(className)
}
/**
* @param { HTMLImageElement } el
*/
export function observe(el) {
if (el.classList.contains(V_LAZY_LOADED)) {
el.classList.remove(V_LAZY_LOADED)
}
io.observe(el)
}
/**
* @param { HTMLImageElement } el
*/
export function unobserve(el) {
io.unobserve(el)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
我们主要向外部暴露两个 API(observe
和 unobserve
) 用于对 Image 元素进行观察与取消观察。
重点是放在 new IntersectionObserver
的过程。
我们先获取相应的 Image 元素,然后获取附着在元素上面的 src
、data-src
和
data-err
属性。
然后在缓存中创建一个 Image
对象并加载相应的图片。
注意:此时 DOM 上的 img 还是有图片的,我们可以预先用一个简单的图片填充, 待我们的图片加载完成后,将 DOM 图片 img.src 更换成我们想要的图片链接即可。
其原理是通过浏览器缓存实现的。
# 兼容性问题
对于一些不兼容 IntersectionObserver
的浏览器,我们可以使用 profile
做一个兼容性处理。