图像,就像大多数媒体格式一样,可以非常沉重,需要大量的时间来加载,我们作为Web开发人员被教导尽可能地压缩它们,为视网膜显示器提供2x版本......并且当有意义时懒惰地加载它们。
问题是,在什么时候懒惰地加载图像有意义?好吧,如果图像位于页面顶部,它很可能会从一开始就可见。在这种情况下,懒惰地加载是不必要的。
交叉观察器 API
在过去,在页面上检测某个元素的可见性是困难的,开发人员需要自己实现它或使用库来实现这一目标,往往会出现缓慢的结果和容易出现错误的解决方案。
Intersection Observer API以非常干净和性能的方式解决了这个问题,它提供了可订阅的模型,我们可以观察到当一个元素进入视图端口时被通知。
这里有一个简单的例子:
1const observer = new IntersectionObserver(entries => {
2 const rainbowDiv = entries[0];
3 if (rainbowDiv.isIntersecting) {
4 // Do something cool here
5 }
6});
7
8const rainbowDiv = document.querySelector("#rainbowDiv");
9observer.observe(rainbowDiv);
我们必须创建一个IntersectionObserver
的实例,将订阅回调作为其参数,在那里我们检查它是否通过isIntersecting
方法与rainbowDiv
交叉。
正如您可能已经注意到的那样,订阅回调接收了一系列条目,这意味着您可以观察多个元素:
1const observer = new IntersectionObserver(entries => {
2 entries.forEach(entry => {
3 if (entry.isIntersecting) {
4 // ...
5 }
6 });
7});
8
9const allRainbows = document.querySelector(".rainbow");
10observer.observe(allRainbows);
<$>[注] 查看 這篇我們的帖子 另一個例子,在瓦尼拉JavaScript中使用Intersection Observer API。
视觉组成部分
考虑到上面的示例,您可能对如何实现 LazyImage.vue 组件有一个想法:
1[label LazyImage.vue]
2<template>
3 <img :src="https://cdn.jsdelivr.net/gh/andsky/tutorials-images/srcImage" />
4</template>
5
6<script>
7export default {
8 props: ['src'],
9 data: () => ({ observer: null, intersected: false }),
10 computed: {
11 srcImage() {
12 return this.intersected ? this.src : '';
13 }
14 },
15 mounted() {
16 this.observer = new IntersectionObserver(entries => {
17 const image = entries[0];
18 if (image.isIntersecting) {
19 this.intersected = true;
20 }
21 });
22
23 this.observer.observe(this.$el);
24 },
25}
26</script>
我们正在设置观察者在安装
链接中,确保该组件已经连接到DOM. 我们可以使用this.$el
访问组件元素,然后将其传输到观察
方法。
然后,我们在计算属性中使用的状态中有一个交叉
的旗帜,即srcImage
,所以当它与视图端口交叉时,它会返回 src prop 的实际值,但否则它会返回一个空串,浏览器不会加载任何东西。
关于表演的一篇文章
请记住,观察元素需要内存和CPU,这就是为什么我们不需要就停止观察它们的重要原因。
我们在IntersectionObserver
实例中有几种方法:
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
由于在我们的情况下,我们只有一个元素,任何一个元素都会正常工作:
1[label LazyImage.vue]
2<template>
3 <img :src="https://cdn.jsdelivr.net/gh/andsky/tutorials-images/srcImage" />
4</template>
5
6<script>
7export default {
8 props: ['src'],
9 data: () => ({ observer: null, intersected: false }),
10 computed: {
11 srcImage() {
12 return this.intersected ? this.src : '';
13 }
14 },
15 mounted() {
16 this.observer = new IntersectionObserver(entries => {
17 const image = entries[0];
18 if (image.isIntersecting) {
19 this.intersected = true;
20 this.observer.disconnect();
21 }
22 });
23
24 this.observer.observe(this.$el);
25 },
26 destroyed() {
27 this.observer.disconnect();
28 }
29}
30</script>
首先,我们在设置this.intersected = true
后立即停止观察,因为图像在那个时候已经被加载,继续观察就没有意义。
此外,如果它被破坏,观察某个组件没有意义,这就是为什么我们添加了破坏
链接,以便在发生这种情况时停止观察它。
如果您尝试使用图片列表的LazyImage组件,并在浏览器的DevTools中打开网络卡,您将看到这些图像正在加载,当它们进入视图端口时。
<$>[注] 如果您对 Vue 应用程序中的懒惰加载图像感兴趣,但不希望自己实现交叉观察员代码,则可以查看 vue-clazy-load组件。
包装上
Lazy Loading 图像可以提高页面性能,特别是 页面加载时间和时间到交互式性能指标。
IntersectionObserver API 尚未得到所有现代浏览器的完全支持(https://caniuse.com/#feat=intersectionobserver),但有 polyfill由 w3c 维护,因此您今天可以在应用中使用它。
保持冷静