iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0

一天的開始

你是新創公司 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

現在畫面上能看到幾張圖片,下載資源的數量就是幾張,哎呀!這不是解決了嗎,恭喜你完成了一項驚人的創舉,現在我們可以拿著這些結果和比對的數據去交差了!

準備下班

完美的交付了任務之後,站起身子,伸伸懶腰,美好的一天即將結束!收拾收拾準備下班囉。


上一篇
React.js 職場實戰!最常見的需求與解法! — 前言
下一篇
React.js 職場實戰!圖片 Infinite List
系列文
React.js 職場實戰!最常見的需求與解法!3
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言