iT邦幫忙

0

筆記|React - 番外 - 自製 hook 確認圖片完成載入,才執行後續動作

Kim 2023-04-23 17:28:59867 瀏覽
  • 分享至 

  • xImage
  •  

☁️ 背景脈絡

最近在開發 side project,運用到 GSAP 製作網頁動畫,其中 ScrollTrigger 提供好用的 markers key,可以標記出動畫會在網頁卷軸接觸到哪個位置時開始、結束,但我的畫面上始終有個圖片動畫的 markers 標記不在我設定的地方,可其他文字動畫都在對的地方,後來爬了文才體悟到「圖片是非同步載入的,而動畫在圖片完成載入前就先架完,自然抓不到圖片的正確位置」,於是就往「如何在 React 確認圖片完成載入才執行後續動作」去找資料,找到了作者 Alejandro Martinez 自製確認圖片完成載入的 hook ,非常讚,以下筆記


🏞 何謂圖片完成載入?

在使用自製 hook 前,先了解 image element 有一個特別的屬性:complete
它的值為布林值,true 表示圖片完全載入,反之為 false。

以下任一狀況 <img> 會被視為圖片完全載入:

  1. srcsrcset attribute 不存在
  2. srcset attribute 不存在,src attribute 為空值 ""
  3. 圖片資源已經完全取得(fetch),並且已經是可以渲染的狀態
  4. image element 先前已經確認圖片資源可以使用,是隨時可用的狀態
    舉例:承襲上述案例,當我微幅修改我的程式碼(加個 comment),然後儲存 VScode,造成網頁的 Hot Module Replacement(HMR),圖片因為前一次 render 已經載入過,所以 markers 就會跑到正確位置,但當重整頁面,造成 initial render,markers 就會是錯誤的位置
  5. 圖片資源損壞,可能是載入時發生錯誤,或是無法進行載入

✨ 自製確認圖片完成載入的 hook

1. hook 程式碼

原作者的寫法幾乎是一模一樣,只是對方是用 TS,我改成 JS,並做了一些註解

import { useState, useEffect } from 'react';

export const useOnLoadImages = (ref) => {
  // 初始 images 載入狀態為 false
  const [status, setStatus] = useState(false);

  useEffect(() => {
    // 更新 images 載入狀態的函式,接收 images 陣列,並在裡面使用 .complete property,來檢查 image 是否「完成載入」還沒會是 false,只有當收到的 images 陣列「都(.every)」完成載入,值才會是 true,set 不一樣的值觸發 re-render,最後傳出去的值變成 true,使用這個 hook 的元件也會收到 true 值
    const updateStatus = (images) => {
      setStatus(
        images.map((image) => image.complete).every((item) => item === true)
      );
    };

    // ref 不存在就可以直接 return 掉
    if (!ref.current) return;

    // 抓 ref 裡面所有的 img,並將 NodeList 改成陣列型別
    const imagesLoaded = Array.from(ref.current.querySelectorAll('img'));

    // 如果陣列為空,沒有任何 img 要載入,也可以說是確認 images 載入完成,更新 status 為 true
    if (imagesLoaded.length === 0) {
      setStatus(true);
      return;
    }

    // 在每個圖片上面綁監聽器,監聽 load 跟 error 事件,這兩個事件發生都代表著 .complete 為 true,就調用 updateStatus 更新現在整體的圖片載入狀況
    // 監聽器第三個參數,當 once: true,代表這個監聽器只會執行一次,就會自動移除
    imagesLoaded.forEach((image) => {
      image.addEventListener('load', () => updateStatus(imagesLoaded), {once: true,});
      image.addEventListener('error', () => updateStatus(imagesLoaded), {once: true,});
    });
  }, [ref]);

  // 回傳 images 載入狀態
  return status;
};

2. 使用在需要的元件

我在第 12 行加上一個 if 判斷式:只有圖片載入完成才上動畫,搭配 useEffect 的特性,即便是 initial render,markers 也會在正確的位置出現 🥳

// 記得引入
import { useEffect, useRef } from 'react';
import { useOnLoadImages } from '../hooks/useOnLoadImages';

function Part1() {
  // 只需要確認 part1 內的圖片元素即可
  const part1 = useRef();
  const isImagesLoaded = useOnLoadImages(part1);

  useEffect(() => {
    // 只有圖片載入完成才上動畫
    if (isImagesLoaded) {
        // 設計的動畫(略)..
        // ..
    }
    
    // isImagesLoaded 值有更動就會觸發此 useEffect
  }, [isImagesLoaded]);


  return (
    <div ref={part1}>
      <img src={ooo} alt="ooo" className="ooo" />
      <img src={xxx} alt="xxx" className="xxx" />
    </div>
  );
}

以上是我的使用情境,也很歡迎大家點入原作者的文章看他 demo 另一個使用情境


🙏🏻 參考資料

Scrolltrigger pins in wrong position on intitial load (React app) #comment-133157

Mozilla - HTMLImageElement: complete property

Alejandro Martinez - How to detect images loaded in React

響應式圖片 Responsive Images,img srcset 的用法


有任何想法歡迎提出交流:)
.
筆者小記:開發 side project 常伴隨著精彩的踩坑之旅 ε-(´∀`; )


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言