一、判斷是否為GIF圖片類型
在JavaScript中,判斷用戶上傳的文件是否為GIF文件類型時,通常可以通過檢查文件的type屬性或文件的拓展名來判斷,但是由于文件拓展名可以輕易被用戶修改,type屬性是由瀏覽器根據文件拓展名猜測得出的,因此這種判斷方式并不總是準確的。
為了更準確的判斷文件類型,需要讀取文件的頭部字節,并檢查這些字節是否符合GIF文件的規范。
要檢查文件頭部字節以確定文件是否為GIF格式,可以使用JavaScript的FileReader API來讀取文件的前幾個字節,并與GIF文件的魔數(Magic Number)進行比較。GIF文件的魔數是GIF87a或GIF89a,它們位于文件的前幾個字節中。
// 文件輸入元素
const fileInput = document.getElementById('file-input')// 監聽文件變化事件
fileInput.addEventListener('change', async fucntion(e) {// 獲取用戶選擇的文件const file = e.target.files[0]if(file) {const isGif = await checkGifFileType(file)if(isGif) {// 是gif} else {// 不是gif}}
})
/*** 檢查文件類型是否是gif* * @param {*} file * @returns */
export function checkGifFileType(file) {// 創建一個FileReader對象const reader = new FileReader();// 讀取文件的前幾個字節reader.readAsArrayBuffer(file.slice(0, 6)); // GIF的魔數只需要6個字節return new Promise((resolve, reject) => {reader.onload = function (e) {// 將ArrayBuffer轉換為Uint8Array以便讀取字節const arrayBuffer = e.target.result;const uint8Array = new Uint8Array(arrayBuffer);// GIF文件的魔數可以是'GIF87a'或'GIF89a',轉換為ASCII碼分別為[71, 73, 70, 56, 55, 97]或[71, 73, 70, 56, 57, 97]// 檢查文件的前6個字節是否匹配GIF的魔數const gif87a = [71, 73, 70, 56, 55, 97];const gif89a = [71, 73, 70, 56, 57, 97];if ((uint8Array[0] === gif87a[0] &&uint8Array[1] === gif87a[1] &&uint8Array[2] === gif87a[2] &&uint8Array[3] === gif87a[3] &&uint8Array[4] === gif87a[4] &&uint8Array[5] === gif87a[5]) ||(uint8Array[0] === gif89a[0] &&uint8Array[1] === gif89a[1] &&uint8Array[2] === gif89a[2] &&uint8Array[3] === gif89a[3] &&uint8Array[4] === gif89a[4] &&uint8Array[5] === gif89a[5])) {resolve(true); // 是GIF文件} else {resolve(false); // 不是GIF文件}};reader.onerror = function (error) {reject(error);};});
}
二、解析GIF,并在canvas上播放
GIF本質上和視頻一樣,都是一幀一幀的圖片拼合而成,所以,在canvas上播放GIF功能實現的要點,就是獲取具體某一幀的資源。
- 使用ImageDecoder API解析GIF每一幀的圖像資源;
- 將該圖像資源繪制在canvas畫布上;
注意:目前ImageDecoder僅Chrome瀏覽器支持,如果考慮兼容性,可以使用libgif.js等第三方庫去解析GIF。
1、資源獲取
使用fetch
方法獲取GIF圖片資源,注意跨域問題。
fetch("xxx.gif").then((response) => {// response.body 就是圖像資源數據
});
2、使用ImageDecoder解析
imageDecoder
對象就包含了一系列的屬性和方法,用來對解析好的圖像數據進行各種各樣的處理。
const imageDecoder = new ImageDecoder({ data: response.body, type: "image/gif"
});
獲取GIF第一幀的圖形數據,則可以:
imageDecoder.decode({ frameIndex: 0
}).then((result) => {// result 對象就是解析后的結果
});
result 對象包括下面這些屬性,其中result.image
?的返回值是一個?VideoFrame
?對象,包含很多屬性和方法,例如,幀圖像的編碼尺寸,顯示尺寸,時間戳,時間間隔等,可以作為 ImageSource 繪制在 canvas 畫布上。
{// 解碼的圖像image: VideoFrame,// 如果為true,則表示該圖像包含最終的完整細節輸出。complete: boolean
}
result.image.timestamp:當前幀出現的時間戳,單位為微分秒,即萬分之一秒
result.image.duration:當前幀持續的時長,單位為微分秒,即萬分之一秒
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");context.drawImage(result.image, 0, 0);
?
3、簡單封裝
export function giftDecoder(url) {return new Promise((resolve, reject) => {fetch(url).then(async (response) => {// response.body 就是圖像資源數據const imageDecoder = new ImageDecoder({ data: response.body, type: "image/gif" });let _d = await imageDecoder.decode({ frameIndex: 0 })console.log(imageDecoder, _d)const track = imageDecoder.tracks.selectedTrack;console.log(track)let arr = []let totalTime = 0for(let i = 0; i < track.frameCount; i++) {let result = await imageDecoder.decode({ frameIndex: i})if(result) {// result 對象就是解析后的結果// 1000000// timestamp:當前幀出現的時間戳,單位為微分秒// duration:當前幀持續的時長,單位為微分秒arr.push(result)if(i == track.frameCount - 1) {totalTime = result.image.timestamp + result.image.duration}} else {reject()break}}console.error('gif數組', arr)resolve({totalTime,list: arr})});})
}
// gif圖特殊處理
const gifData = await giftDecoder(gifUrl)console.error('gif圖特殊處理', gifData)
參考文檔
https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame
https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder