使用require.context實現優雅的預載入

加伊bky發表於2023-05-05

前言

在前端開發中,對頁面花裡胡哨度[注1]要求越高的頁面,用到的圖片、音訊什麼的就越多,比如什麼結婚請柬、展會請柬、釋出會宣傳頁、資料大屏。雖然現在瀏覽器不允許網頁在沒有使用者互動的情況下播放音訊,但是,我們依舊要在頁面展現的同時,準備好所有的靜態資源。

注1:花裡胡哨度(garish degree),又名難做指數,江湖人稱領導開心點

醜陋的預載入

預載入即提前載入,瀏覽器在請求一張圖片時,會快取到本地,在下次請求同樣的地址時,會直接在本地快取讀取(304),在本地讀取的時間基本可以忽略不計。如果我們能夠在圖片未載入完成時給使用者一個載入進度,提示使用者:“急什麼,馬上完事!”,則能夠有效的提升使用者體驗。

單張預載入

相信同學都瞭解圖片的預載入:

let img = new Image()
img.src = "@/../../xx.png"
img.onload = () => {
	//...
}

這是為大家所熟知的預載入方式,但是這種方法只適用於單張圖片的預載入。

那多張怎麼做呢?

多張預載入

很簡單,我們給圖片們定義一個陣列就好了

let imagesPathArr = ["@/../../xx.png","@/../../yy.png","..."];

然後我們再用迴圈去載入這些圖片

let count = 0        
for (let item of imagesPathArr) {
          let img = new Image()
          img.src = item
          img.onload = () => {
            count++
            if (count === imagesPathArr.length) {
                // ... 載入完成
    		}
  		}
}

我們甚至可以透過count/imagesPathArr.length算出載入的百分比 。

沒錯,但是這種方法載入十張圖片還可以,那載入一百張呢?

同學說:“我可以把圖片從0-99命名,載入時只需要迴圈一百次就可以了!”

可以,那麼假如我們用python寫了一個重新命名指令碼,把這一百張圖片從0-99命名完成。

那麼我們的程式碼就長這樣:

for(let i = 0;i<=99;i++){
	let img = new Image()
      img.src = `@/../../${i}.png`
      img.onload = () => {
      count++
      if (count === imagesPathArr.length) {
          // ... 載入完成
      }
  	}
}

ok,看起來沒有任何問題,實際上也沒有任何問題。

但是在使用過程中,我們會發現,圖片的格式不一定是統一的(當然你可以將他們轉換成統一的),而且這種方式看起來太醜了,一點也不夠優雅。

那麼有沒有一種方式,優雅的預載入呢?有。

優雅的預載入

要實現優雅的預載入,我們要優哪些方面?

  • 第一,我們無需知道載入的圖片有多少;
  • 第二,我們無需知道載入的圖片叫什麼;
  • 第三,我們無需知道圖片的格式是什麼。

他?的,這聽起來就優雅,相當於什麼都不用幹,就把預載入做出來了!

但是,眾所周知,瀏覽器環境沒有直接操作檔案系統的能力,我們無法像node一樣,直接使用fs,怎麼才能做到如上所說的呢?從第一步來看,我們至少要遍歷一個父級資料夾吧?

本期的主角登場

require.context

它是一個webpack的api,可以透過這個方法獲取一個特定的上下文,用來實現檔案的批次自動化匯入,如果你使用vite,那麼可以使用 import.meta.globEager(),本文只用require.context舉例。

好像這個api已經存在了好久了,但是我是最近才知道的?,在這裡分享給還沒用過的同學。

使用語法如下:

        let requireModule = require.context(
          "../../../public/static/img", // 需要遍歷的路徑
          false, // 是否遞迴,設定為true會遞迴到最後一級資料夾
          /\.png|\.webp|\.jpg|\.jpeg|\.bmp|\.gif$/ //匹配的正規表示式
        );

上述程式碼匹配了常用的圖片格式。

如果我們迴圈它的key(),會得到類似./xxx.png的項,所以,只要去掉./就得到了資料夾下所有的圖片。

所以,我們可以做一個陣列來儲存所有的圖片路徑:

let imagesPathArr = [];
for (var i = 0; i < requireModule.keys().length; i++) {
    imagesPathArr.push("/static/img/" + requireModule.keys()[i].substr(2, requireModule.keys()[i].length));
}

這樣,imagesPathArr就擁有了我們指定資料夾下所有的圖片路徑了,我們根本無需關心圖片有多少、叫什麼、什麼格式。

下面直接對imagesPathArr進行迴圈(跟上面一樣),匯入所有圖片:

        let count = 0
        for (let item of imagesPathArr) {
          let img = new Image()
          img.src = item
          img.onload = () => {
            count++
            if (count === imagesPathArr.length) {
                // 載入完成
            }
          }
        }

最後,我們把所有的邏輯封裝成一個函式,並給他套上promise

    async loadImgs() {
      await new Promise((resolve, reject) => {
        this.$store.dispatch('loadingStart', {
          text: "正在載入資源"
        })
        let requireModule = require.context(
          "../../../public/static/img",
          false,
          /\.png|\.webp|\.jpg|\.jpeg|\.bmp|\.gif$/
        );
        let imagesPathArr = [];
        for (var i = 0; i < requireModule.keys().length; i++) {
          imagesPathArr.push("/static/img/" + requireModule.keys()[i].substr(2, requireModule.keys()[i].length));
        }
        let count = 0
        for (let item of imagesPathArr) {
          let img = new Image()
          img.src = item
          img.onload = () => {
            count++
            if (count === imagesPathArr.length) {
              this.$store.dispatch('loadingDone')
              resolve()
            }
          }
        }
      })
    },

我們只需在合適的時機,呼叫該函式,即可全自動的預載入圖片了,而且日後往資料夾內新增或者刪除圖片,都不用管這一段邏輯,它依然可以穩健執行!如果你有載入音訊的需求,也是同理,在正則部分加一個.mp3什麼的,使用audio.onload即可!

相關文章