React.js 职场实战!图片 Lazy Loading

一天的开始

你是新创公司 Imager 底下的前端工程师,Imager 提供的服务非常简单,就是能在网页浏览各式各样的图片,网页已经上线并正常的运作中,我们来看看他的模样。

看起来一切正常,但此时 PM 却出现在你的身旁,告诉你:

「网页有点慢耶,可不可以帮忙优化一下⋯」

找出问题

网页慢的原因我们可以简单的归纳一下:

  • 打包的档案太大
  • 网页要下载的资源太多
  • 网路太慢
  • 手机太烂

首先我们先剔除後面两点,因为这两个是我们无法控制的。所以原因肯定是出在前面两者之间,而经过了初步的诊断,Imager 也就一个首页和图片列表而已,打包後的大小实在是没什麽问题,那麽原因就是⋯

「网页要下载的资源太多」

至於 Imager 网页上需要下载的资源,只能是「图片」了吧!检查了一下发现,首页的页面上的列表直接把 100 张图片都放上去了,这就代表着使用者一进页面就会开始下载 100 张图片!

其实从上方的图片能看出来,画面中能显示的图片数量是很有限的,顶多就那 6 张图,如果使用者看完 6 张图就离开了网站,那其他 94 张图的资源就是被浪费掉了!

怎麽解决

基於上面的情境,我们知道现在要做的事情就是让图片「按需加载」,也就是出现在画面上的图片才去拿资源,这样就能为使用者节省非常多的资源,也很直接地提升我们网页的体验。

这时你的脑中突然就闪过了「Lazy Loading」的字眼,没错,就决定是你了!

快乐的 Coding 时光

确定了实作的方向後,就剩下开始动工啦!首先我们可以开启现在的 Imager 看看他最原始的模样:

Edit 02-with-count

为了证明加入「Lazy Loading」真的有解决问题,我在左上角加入一个计数器,当有 下载资源就会让数字加 1,简单明了。

实作重点

通常决定了解决的方向後,我习惯会列出能想到的实作方法:

  • 原生 Intersection Observer API
  • 第三方套件

因为之前就有看过使用 Intersection Observer 实作 Lazy Loading 的文章,所以我已经掌握了相关的技巧以及知识,这时候我就会多一项选择。

如果情况允许,也许使用套件也是个不错的选择,简单、容易上手,但是否有人维护和是否有文档也要列入考量,因为专案不一定之後由你维护,可不是每个人都懂这个套件怎麽使用啊!

经过一番折腾,我决定使用 Intersection Observer 自己实作,原因是用它来实现「Lazy Loading」真的很简单,特别用套件来维护的话会让专案的大小增加,不是很理想。

接着就来说明一下实作的部分吧!建议搭配已经准备好的范例一起服用。

Edit 03-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」的网页:

Edit 03-lazy-loading

现在画面上能看到几张图片,下载资源的数量就是几张,哎呀!这不是解决了吗,恭喜你完成了一项惊人的创举,现在我们可以拿着这些结果和比对的数据去交差了!

准备下班

完美的交付了任务之後,站起身子,伸伸懒腰,美好的一天即将结束!收拾收拾准备下班罗。


<<:  【Day02】Git 版本控制 - 浅谈版本控制

>>:  登录档改造(一)--炫技和纯兴趣的杂耍玩法

Azure Private Vetwork 手把手教学

就是不爱PublicIP之Private Endpoint 很实用的技术实作文,值得让更多需要的人知...

[Android Studio 30天自我挑战] ImageView元件介绍

现在在手机或是平板上都会许多图片的应用,这次介绍ImageView与ImageButton这两个元件...

Terraform

Terraform 前言 今天介绍一下可以快速建立GCP各服务的套件Terraform,那麽Terr...

Kubernetes基础功能教学

#Why Kubernetes? Kubernetes(K8S)是一个可以帮助我们管理微服务(mic...

成为工具人应有的工具包-23 FileTypesMan

FileTypesMan 今天来认识这过看名字我也不清楚是啥的小工具,判断档案类型ㄉㄇ? FileT...