一個有必要實現的需求
因為專案中需要使用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)
})
};