一、判断是否为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