赞
踩
在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功能实现的要点,就是获取具体某一帧的资源。
注意:目前ImageDecoder仅Chrome浏览器支持,如果考虑兼容性,可以使用libgif.js等第三方库去解析GIF。
使用fetch
方法获取GIF图片资源,注意跨域问题。
- fetch("xxx.gif").then((response) => {
- // response.body 就是图像资源数据
- });
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);
- 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 = 0
- for(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)
gifuct-js是由Matt Way开发的一个轻量级JavaScript库,专门用于处理和创建GIF动图。它提供了强大的API,让开发者能够轻松地解码、编码、操作及优化GIF文件,适用于网页应用或任何需要在前端处理GIF的场景。
技术分析
npm install gifuct-js
此解码器使用**js-binary-schema-parser**解析 GIF 文件。这意味着要解码 GIF 文件,首先需要将其转换为 Uint8Array
缓冲区。
- import { parseGIF, decompressFrames } from 'gifuct-js';
-
- var promisedGif = fetch(gifURL)
- .then(resp => resp.arrayBuffer())
- .then(buff => {
- var gif = parseGIF(buff);
- var frames = decompressFrames(gif, true);
- return gif;
- });
- import { parseGIF, decompressFrames } from 'gifuct-js';
-
- var oReq = new XMLHttpRequest();
- oReq.open("GET", gifURL, true);
- oReq.responseType = "arraybuffer";
-
- oReq.onload = function (oEvent) {
- var arrayBuffer = oReq.response; // 注意:不是 oReq.responseText
- if (arrayBuffer) {
- var gif = parseGIF(arrayBuffer);
- var frames = decompressFrames(gif, true);
- // 处理帧数据
- }
- };
-
- oReq.send(null);
decompressFrames(gif, buildPatch)
函数返回一个包含所有 GIF 图像帧及其元数据的数组。
- {
- // 每个像素的调色板索引
- pixels: [...],
- // GIF 帧的尺寸(见处置方法)
- dims: {
- top: 0,
- left: 10,
- width: 100,
- height: 50
- },
- // 此帧应显示的毫秒数
- delay: 50,
- // 处置方法(见下文)
- disposalType: 1,
- // 指向像素数据的颜色数组
- colorTable: [...],
- // 可选的代表透明度的色彩索引(见下文)
- transparentIndex: 33,
- // 绘制准备好的 Uint8ClampedArray 颜色补丁信息
- patch: [...]
- }
如果 dcompressFrames()
函数的 buildPatch
参数为 true
,解析器不仅会返回解析和解压缩的 GIF 帧,还会为每个 GIF 帧图像创建可直接用于画布的 Uint8ClampedArray
数组,以便使用 ctx.putImageData()
等方式轻松绘制。这是常见需求,但由于它涉及到透明度假设,所以设为可选项。
像素数据是以每个像素的索引列表存储的。这些索引指向 colorTable
数组中的值,表示每个像素应绘制的颜色。GIF 的每一帧可能不是全尺寸,而是一个需要在特定位置绘制的补丁。disposalType
定义了如何在 GIF 画布上绘制该补丁。大多数情况下,该值为 1
,表示 GIF 帧应简单地覆盖现有的 GIF 画布,而不改变补丁尺寸之外的任何像素。
如果一帧定义了 transparentIndex
,则意味着像素数据中与该索引匹配的任何像素不应被绘制。在使用画布绘图时,这表示将此像素的 alpha 值设置为 0
。
- import { parseGIF, decompressFrames } from 'gifuct-js'
-
- export function giftDecoder(url) {
- return new Promise((resolve, reject) => {
-
- let arr = []
- let totalTime = 0
-
- if('ImageDecoder' in window) {
- fetch(url).then(async (response) => {
- // response.body 就是图像资源数据
- let arr = []
- let totalTime = 0
-
- 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)
-
- for(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, response)
-
- resolve({
- totalTime:totalTime/1000000 ,
- list: arr
- })
- });
- } else {
-
- let oReq = new XMLHttpRequest();
- oReq.open("GET", url, true);
- oReq.responseType = "arraybuffer";
-
- oReq.onload = async function (oEvent) {
- let arrayBuffer = oReq.response; // 注意:不是 oReq.responseText
- if (arrayBuffer) {
- let gif = parseGIF(arrayBuffer);
- let frames = decompressFrames(gif, true);
- // 处理帧数据
- let accumulatedTime = 0
-
- const _canvas = document.createElement('canvas')
- const _ctx = _canvas.getContext('2d')
- _canvas.width = gif.lsd.width
- _canvas.height = gif.lsd.height
-
- for(let i = 0; i < frames.length; i++) {
- let frame = frames[i]
- // _ctx.clearRect(0, 0, _canvas.width, _canvas.height)
-
- let imageData = _ctx.createImageData(frame.dims.width, frame.dims.height)
- imageData.data.set(frame.patch)
- _ctx.putImageData(imageData, frame.dims.left, frame.dims.top)
- let _blob = await canvasToBlob(_canvas, 'image/png')
- let _url = URL.createObjectURL(_blob)
-
- const img = new Image()
- img.src = _url
- await imgOnload(img)
-
- totalTime += frame.delay
- arr.push({
- timestamp: accumulatedTime,
- duration: frame.delay ,
- img: img
- })
-
- accumulatedTime += frame.delay
- }
-
- resolve({
- totalTime: totalTime/1000,
- list: arr
- })
- // console.error('xxx', totalTime, arr)
- } else {
- reject()
- }
- };
-
- oReq.send(null);
- }
-
-
- })
- }
-
- function canvasToBlob(canvas, type, quality) {
- return new Promise(resolve => {
- canvas.toBlob((blob) => {
- resolve(blob)
- }, type, quality)
- })
- }
-
- function imgOnload(img) {
- return new Promise(resolve => {
- img.onload = function() {
- resolve()
- }
- })
- }
https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame
https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。