offscreenCanvas+worker+IndexedDB實現無感大量圖片快取

2023-11-17 18:01:37

一個有必要實現的需求

因為專案中需要使用canvasTexture(一個threejs3d引擎中的材質型別),繪製大量的圖片,每次使用都會請求大量的oss圖片資源,雖然重複請求會有磁碟快取但畢竟這個磁碟快取時效過短,

這裡需要了解一下知識才能正常閱讀。

Transferable objects https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects

Web Worker https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API

OffScreenCanvas https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas

需要注意專案所處瀏覽器環境是否支援其中的某些Api



  • 因為有了將有了將圖片快取到原生的需求,那麼大量的資源快取必然是使用indexedDB了

  • 其次為了方便儲存和使用就需要將圖片專為Blob物件。我們如果在程式中批次的將 canvasTexture 輸出為圖片並專為Blob物件並存到原生的話,會因為大量長時間的佔用主執行緒造成頁面渲染幀時隔過長,造成卡頓影響使用者體驗,

  • 那麼我們就需要將canvasTexture輸出圖片和轉為Blob物件這個耗時的過程放到worker中進行

  • 而如果要在worker中進行操作我們需要用到OffScreenCanvas來進行圖片的繪製輸出和轉為Blob物件

  • 雖然worker可以傳遞OffScreenCanvas物件但是無法傳遞它的渲染空間Context所以我們只能在主執行緒中把canvasTexture中的畫面輸出為ArrayBuffer然後傳遞給worker中新建立的OffScreenCanvas然後通過OffScreenCanvas重新繪製並輸出為Blob物件返回給主執行緒進行儲存(ArrayBuffer,和 Blob都是可轉移物件Transferable object 所以我們不需要擔心它們的通訊效率)自此這個流程就算完成了

這段程式碼是對普通圖片進行快取操作

    //此段以及下一段程式碼中都使用了localforage(一個封裝了web端多個本地儲存策略的npm包)這個Api作為儲存策略
    setImageLocalCache(image, key) {
        const cacheKey = key
        const ofsCanvas = new OffscreenCanvas(image.width, image.height);
        let context = ofsCanvas.getContext('2d')
        context.drawImage(image, 0, 0, image.width, image.height)
        const imageData = context.getImageData(0, 0, ofsCanvas.width, ofsCanvas.height);
        const dataArray = imageData.data; //Unit8ClampedArray 
        const arrayBuffer = dataArray.buffer; // ArrayBuffer
        const worker = new Worker('worker/makeBlobCache.js')

        worker.postMessage({
            arrayBuffer,
            width: image.width,
            height: image.height
        }, [arrayBuffer])

        context.clearRect(0, 0, ofsCanvas.width, ofsCanvas.height)
        context = null

        worker.onmessage = (e) => {
            localforage.setItem(cacheKey, e.data).then(() => {
                URL.revokeObjectURL(URL.createObjectURL(e.data)) // 儲存結束後釋放Blob物件
            })
            worker.terminate(); //釋放worker執行緒
        }
    }

這段程式碼是使用快取的資源操作

    let blob = localforage.getItem(cacheKey)
    if(blob) {
      const image = new Image()
      image.src = URL.createObjectURL(blob)
      blob = null
      image.onerror = (e) => {
        console.log(e)
      }
      image.onload = () => {
        console.log('執行到這裡圖片就載入完成了')
        URL.revokeObjectURL(url)
      }
    }

這段程式碼是上述兩段程式碼中的worker檔案程式碼

self.onmessage = (e) => {
    const arrayBuffer = e.data.arrayBuffer;
    const width = e.data.width;
    const height = e.data.height;
    const uint8View = new Uint8ClampedArray(arrayBuffer);

    const imageData = new ImageData(uint8View, width, height); 
    const offscreen = new OffscreenCanvas(width, height)
    let ctx = offscreen.getContext('2d')

    ctx.putImageData(imageData, 0, 0)
    offscreen.convertToBlob({
        type: 'image/png',
        quality: 1
    }).then(blob => {
        ctx.clearRect(0, 0, offscreen.width, offscreen.height);
        ctx = null;
        self.postMessage(blob)
    })

};