你是新创公司 Imager 底下的前端工程师,Imager 提供的服务非常简单,就是能在网页浏览各式各样的图片,网页已经上线并正常的运作中,我们来看看他的模样。
看起来一切正常,但此时 PM 却出现在你的身旁,告诉你:
网页慢的原因我们可以简单的归纳一下:
首先我们先剔除後面两点,因为这两个是我们无法控制的。所以原因肯定是出在前面两者之间,而经过了初步的诊断,Imager 也就一个首页和图片列表而已,打包後的大小实在是没什麽问题,那麽原因就是⋯
至於 Imager 网页上需要下载的资源,只能是「图片」了吧!检查了一下发现,首页的页面上的列表直接把 100 张图片都放上去了,这就代表着使用者一进页面就会开始下载 100 张图片!
其实从上方的图片能看出来,画面中能显示的图片数量是很有限的,顶多就那 6 张图,如果使用者看完 6 张图就离开了网站,那其他 94 张图的资源就是被浪费掉了!
基於上面的情境,我们知道现在要做的事情就是让图片「按需加载」,也就是出现在画面上的图片才去拿资源,这样就能为使用者节省非常多的资源,也很直接地提升我们网页的体验。
这时你的脑中突然就闪过了「Lazy Loading」的字眼,没错,就决定是你了!
快乐的 Coding 时光
确定了实作的方向後,就剩下开始动工啦!首先我们可以开启现在的 Imager 看看他最原始的模样:
为了证明加入「Lazy Loading」真的有解决问题,我在左上角加入一个计数器,当有 下载资源就会让数字加 1,简单明了。
通常决定了解决的方向後,我习惯会列出能想到的实作方法:
因为之前就有看过使用 Intersection Observer 实作 Lazy Loading 的文章,所以我已经掌握了相关的技巧以及知识,这时候我就会多一项选择。
如果情况允许,也许使用套件也是个不错的选择,简单、容易上手,但是否有人维护和是否有文档也要列入考量,因为专案不一定之後由你维护,可不是每个人都懂这个套件怎麽使用啊!
经过一番折腾,我决定使用 Intersection Observer 自己实作,原因是用它来实现「Lazy Loading」真的很简单,特别用套件来维护的话会让专案的大小增加,不是很理想。
接着就来说明一下实作的部分吧!建议搭配已经准备好的范例一起服用。
首先看到 ,我们将 src 拿掉,因为 src 会触发浏览器下载图片资源,取而代之 src 会暂存在 data-src 以利之後使用。
// App.js
import React from "react";
import Image from "./components/Image";
import useCount from "./hooks/useCount";
import generateImages from "./utils/generateImages";
import { View, Title, ImageBlock, ImageCount } from "./style";
const images = generateImages({ count: 100 });
const App = () => {
const { count, addCount } = useCount();
return (
<View>
<ImageCount>图片载入数量:{count}</ImageCount>
<Title>Imager</Title>
<ImageBlock>
{images.map(image => (
<Image
key={image.id}
data-src={image.src} // 暂存 src
className="lazy-image" // 需要加入 lazy loading 的标记
onLoad={addCount}
// 为了避免浏览器自动去下载资源,注解它
// src={image.src}
/>
)}
</ImageBlock>
</View>
);
};
export default App;
接着我们加入一个 useLazyLoading 的 hook,将有 lazy-image 标签的 取出来并交给 Intersection Observer 做监听:
// useLazyLoading.js
import { useEffect } from "react";
const useLazyLoading = () => {
useEffect(() => {
// entries : 所有被 observer 监听的元件,即 <Image />
// observer: 监听器
const callback = (entries, observer) => {
entries.forEach((entry) => {
// 如果监听的元件目前在视野范围内
if (entry.isIntersecting) {
const image = entry.target; // 取得 <Image />
image.setAttribute("src", image.dataset.src); // 将 data-src 塞入 src 让浏览器触发下载资源
image.removeAttribute("data-src"); // 移除 data-src
observer.unobserve(image); // 取消监听 <Image />
}
});
};
const observer = new IntersectionObserver(callback);
const lazyImages = document.querySelectorAll(".lazy-image");
// 取得所有 <Image className="lazy-image" /> 并让 observer 开始监听
lazyImages.forEach((image) => observer.observe(image));
}, []);
};
export default useLazyLoading;
最後将 useLazyLoading 加入到我们的 App.js 之中就大功告成:
// App.js
import React from "react";
import Image from "./components/Image";
import useCount from "./hooks/useCount";
import useLazyLoading from "./hooks/useLazyLoading";
import generateImages from "./utils/generateImages";
import { View, Title, ImageBlock, ImageCount } from "./style";
const images = generateImages({ count: 100 });
const App = () => {
const { count, addCount } = useCount();
useLazyLoading(); // 加入 Lazy Loading
return (
<View>
<ImageCount>图片载入数量:{count}</ImageCount>
<Title>Imager</Title>
<ImageBlock>
{images.map(image => (
<Image
key={image.id}
data-src={image.src}
className="lazy-image"
onLoad={addCount}
/>
)}
</ImageBlock>
</View>
);
};
export default App;
我们来看看加入了「Lazy Loading」的网页:
现在画面上能看到几张图片,下载资源的数量就是几张,哎呀!这不是解决了吗,恭喜你完成了一项惊人的创举,现在我们可以拿着这些结果和比对的数据去交差了!
完美的交付了任务之後,站起身子,伸伸懒腰,美好的一天即将结束!收拾收拾准备下班罗。
就是不爱PublicIP之Private Endpoint 很实用的技术实作文,值得让更多需要的人知...
现在在手机或是平板上都会许多图片的应用,这次介绍ImageView与ImageButton这两个元件...
Terraform 前言 今天介绍一下可以快速建立GCP各服务的套件Terraform,那麽Terr...
#Why Kubernetes? Kubernetes(K8S)是一个可以帮助我们管理微服务(mic...
FileTypesMan 今天来认识这过看名字我也不清楚是啥的小工具,判断档案类型ㄉㄇ? FileT...