有一个Gif图片,我们想要获取它的总帧数,超过一定帧数的图片告知用户不可上传,在服务端有很多现成的库可以使用,这种做法不是很友好,前端需要先将gif上传至服务端,服务端解析完毕后将结果返回,大大降低了用户体验。
那么如何通过js在上传前就拿到它的总帧数来判断呢?本文就跟大家分享一种解决方案,并将其封装成插件发布至npm仓库,欢迎各位感兴趣的开发者阅读本文。
此插件已经发布至npm,采用原生JS编写支持任意一个前端框架,如果你对其实现原理不感兴趣,只是想拿来解决你的实际问题,可以直接通过npm/yarn来安装,命令如下:
# yarn安装
yarn add gif-parser-web
# npm安装
npm install gif-parser-web --save
文档地址请移步:README.md
我们都知道无论什么文件在计算机中都是以流的形式进行存储的,因此我们可以通过读取文件流来拿到它的所有信息。Gif类型的文件也是如此,我们只要能知道它的文件流结构就可以根据它的规则进行解析读取了。
Gif的全称是Graphics Interchange Format,是一种位图,以8位色重现真彩色的图像。采用LZW压缩算法进行编码,可以有效的减少图像文件在网上的传输时间,我们在网站上看到的会动的表情包,基本上都是Gif格式的。
正如上面所说,我们想解析gif就得先知道它的文件流结构,在What's In A GIF网站中我们知道了它是由多种不同类型的块组成,如下所示:
了解完gif的组成结构后,接下来我们来看下如何获取它的数据流,如下所示:
它的解码过程如下图所示:
GIF file stream diagram
注意:在读取过程中,每个块都有自己特殊的编码标记。
我们了解完gif的构成后,接下来我们来看下每一个具体的数据块的编码信息。
该数据块用于标记数据流的开始,位于文件头数据流的上下文内,里面包含了gif的签名与版本信息,它是必须存在的且只有一个。
该块在数据流中占6个字节,其中签名与版本信息各占3个字节,即:
我们以89a格式的gif为例,它的Header信息就如下所示:
GIF header block layout
我们来看下如何用代码来读取。
// 假设我们已经得到了dataView
const signature = dataView.getUint16(0); // 使用getUint16方法从0号位置开始连续获取2个字节的值,转换成转换为Unicode编码为:G I
const version = dataView.getUint16(2); // 使用getUint16方法从2号位置开始连续获取2个字节的值,转换成转换为Unicode编码为:F 8
该数据块中定义了图像在设备中显示所需的参数,位于Header数据块的后面,它是必须存在的且只有一个,其值的坐标是相对于虚拟屏幕左上角计算出来的。
该块在数据流中占7个字节,包含的信息如下所示:
Global Color Table Flag 全局颜色标记,用于标识全局颜色表。如果值为0则表示不存在全局颜色块;如果值为1则表示全局颜色块紧跟于此块之后。
Color Resolution 颜色分辨率,即颜色的位数,有1位、8位、16位、32位等。在gif格式的图像定义中,它的颜色不能超过256种,深度不能超过8位。
Sort Flag 排序标记,0为未设置,1为按重要性递减排序,最重要的颜色在前。
Size of Global Color Table 全局颜色表的大小,如果值为1,则该字段中的值用于计算全局颜色表中包含的字节数。
GIF logical screen descriptor block layout
我们用代码来获取下它的宽度与高度。
// 假设我们已经得到了dataView
const width = this.dataView.getUint16(6, true);
const height = this.dataView.getUint16(8, true);
该数据块包含了一个颜色表,由红-绿-蓝三元组的字节序列构成。正如前面所说,它并非必须存在,如果存在的话它将位于Logical Screen Descriptor块的后面。
所占的字节数为3*2^(N+1),N为全局颜色表的大小 + 1,该数据块在数据流中只存在一个,如下图所示。
GIF global color table block layout
我们来看下代码的实现。
let pos = 0;
const PaletteColorsRGB = [];
const gifInfo = {}
// 解析全局调色板
const unpackedField = getBitArray(dataView.getUint8(10));
if (unpackedField[0]) {
const globalPaletteSize = getPaletteSize(unpackedField);
gifInfo.globalPalette = true;
// 计算全局调色板的大小
gifInfo.globalPaletteSize = globalPaletteSize / 3;
// 调整指针位置
pos += globalPaletteSize;
// 遍历获取此块区域的所有颜色并存起来
for (let i = 0; i < gifInfo.globalPaletteSize; i++) {
const palettePos = 13 + i * 3;
const r = dataView.getUint8(palettePos);
const g = dataView.getUint8(palettePos + 1);
const b = dataView.getUint8(palettePos + 2);
PaletteColorsRGB.push({ r, g, b });
}
}
pos += 13;
// 获取调色板大小函数
function getPaletteSize(palette: Array<number>): number {
return 3 * Math.pow(2, 1 + bitToInt(palette.slice(5, 8)));
}
该数据块包含了处理图形渲染块时需要使用的参数,它只包含了一个数据子块。该块中记录了7种数据的描述,如下所示:
Reserved for Future Use 保留模块。
Disposal Method 处理方法,表示图形在显示后的处理方式。
User Input Flag 用户输入标识,在继续之前是否需要用户输入,如果是0则不需要用户输入,1代表需要用户输入。输入的性质由程序决定(如回车、鼠标点击等) 。
Transparency Color Flag 透明标识,用于描述是否在透明索引字段中给出了透明索引。0:未给出透明索引;1:给出了透明索引 。
GIF graphic control extension block layout
此处我们最关心的就是如何取出gif每一帧的时长,我们来看下代码的实现。
// 假设我们已经得到了dataView且pos可能指向图形控制快
const type = dataView.getUint8(pos);
// 图形控制块
if (type === 0xf9) {
const length = dataView.getUint8(this.pos + 2);
if (length === 4) {
// 获取每一帧的时长
const delay = getFrameDuration(dataView.getUint16(pos + 4, true));
pos += 8;
}
}
一个gif文件可能会包含多个图像,每个图像都以一个图像描述符块开始。这个块在数据流中占10个字节。该块中记录了6种数据的描述,如下所示:
Local Color Table Flag 局部颜色表标志,紧跟在该图像描述符之后的局部颜色表的存在,0:不存在,则使用全局颜色表,1:存在,则使用紧跟其后的Local Color Table数据块 。
Interlace Flag 隔行标志,标识图像是否是隔行的(图像以四遍交错模式交错) 。
Sort Flag 排序标志 - 指示本地颜色表是否已排序。0:未设置排序,1:按重要性递减排序,最重要的颜色在前 。
Size of Local Color Table 局部颜色表的大小 。
GIF image descriptor block layout
该块由一系列子块组成,每个子块的大小最多为255字节,包含对图像中每个像素的活动颜色表的索引, 像素索引按从左到右和从上到下的顺序排列。每个索引必须在活动颜色表的大小范围内,从 0 开始。索引序列使用具有可变长度代码的 LZW 算法进行编码,如下所示。
GIF image data block layout
GIF image data block layout
每解析完一轮Image Descriptor都需要读取下Data Sub-blocks,直至所有子块被读取完毕。
通过前面的了解,我们知道了Gif图像中每个数据块的组成原理,接下来我们就可以编写代码来解决我们所遇到的问题了。
我们将数据块分析章节的思路整理下,核心代码如下所示:
export default class GifParser {
private urlLoadStatus: boolean | undefined = undefined;
private dataView: DataView | undefined;
// 当前指向DataView的指针位置
private pos = 0;
// 当前解析的帧索引
private index = 0;
private gifInfo: gifInfoType = {
valid: false,
globalPalette: false,
globalPaletteSize: 0,
globalPaletteColorsRGB: [],
loopCount: 0,
height: 0,
width: 0,
animated: false,
images: [],
duration: 0,
identifier: "0"
};
constructor(url?: string) {
if (url) {
this.urlLoadStatus = false;
// 解析url,将其转化为DataView格式的数据
fetch(url)
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => {
return new DataView(arrayBuffer);
})
.then((dataView) => {
// GIF加载成功
this.urlLoadStatus = true;
this.dataView = dataView;
});
}
}
/**
* 获取图像信息
* @param gifStream
*/
public async getInfo(gifStream?: File): Promise<gifInfoType> {
// 参数有效性校验
await this.validityCheck(gifStream);
// url与gifStream都未传入则抛出异常
if (this.dataView == null) {
throw new Error("未找到GIF解析源, 请检查参数是否正确传入");
}
// 只解析GIF8格式的图像:使用getUint16获取2个字节十六进制值,判断它是否满足Gif格式的Header块的签名与版本号
// 47 49 为签名信息,转换为Unicode编码为:G I
// 46 38 为版本信息,转换为Unicode编码为:F 8
if (
this.dataView.getUint16(0) != 0x4749 ||
this.dataView.getUint16(2) != 0x4638
) {
return this.gifInfo;
}
// 经过上述判断后,此时的GIF已经有效了
this.gifInfo.valid = true;
// 获取GIF图像的宽,高
this.gifInfo.width = this.dataView.getUint16(6, true);
this.gifInfo.height = this.dataView.getUint16(8, true);
// 获取全局调色板、读取每一帧的图像信息等代码省略,请移步GitHub查看完整代码
}
}
最后,我们将插件打包,写一个简单的demo来测试下。
<meta charset="utf-8">
<title>gifParserPlugin demo</title>
<script src="./gifParserPlugin.umd.js"></script>
<script>
async function getGifInfo(e) {
const gifParser = new gifParserPlugin()
const gifInfo = gifParser.getInfo(e.target.files[0])
gifInfo.then((res) => {
console.log("解析完成", res);
})
}
window.onload = function() {
const input = document.getElementById('input');
input.addEventListener('change', getGifInfo);
}
</script>
<input type="file" id="input">
运行结果如下所示。
来源: 神奇的程序员
在我们开发中,会遇到这样的场景:1.服务器返回Json数据,根据数据类型来显示是图片还是视频。2.前端上传文件,需要指定文件类型才能上传到服务器。这时候就需要使用Js来判断对应文件的类型
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样子对于页面加载性能上会有很大的提升,也提高了用户体验。
在网页图片显示的时候,会发现许多网站采用了先模糊,然后在慢慢清晰的过程,这样的加载用户体验是比较好的,那么如何实现呐?默认加载2张图片,一张缩略图,一张原图,当打开网页的时候默认只显示缩略图
当你的网站使用了大量图片时候,如果一次性全部加载,那么会严重影响网站的速度。通过lazysizes.js插件就能很好解决这个问题,它可以实现图片的延迟加载【懒加载】
图片局部放大效果结合的知识点主要是DOM的操作,以及事件的应用,所以首先要对DOM的操作有一定了解,其次能对事件的应用有一定的累积。
网站图片优化技巧:1、图片名包括关键词,2、Alt标签包括关键词,3、图片周边文本包括关键词,4、GLF和JPGE图画优化,5、在图片链接中运用锚文本关键字
前端需要显示矩形的缩略图,接口返回的图片尺寸大小不一,宽高不相等,需要前端来处理并显示成正方形,类似微信朋友圈图片的效果。那么使用纯css该如何实现不定宽高的图片居中裁剪呢?
目前浏览器对html5的支持越来越好,我们现在不用服务器端,直接在前端利用canvas就可以进行图片的合成了。下面就介绍下如何通过原生js 来生成海报图
现在网页中图片随处可见,但避免不了有时会出现图片资源失败的情况,这里的alt属性是为了当图片加载失败时告诉用户图片信息的 ,能不能美化一下呢?下面给出几种方式
在移动端访问H5页面的时候,长按图片就会把图片保存起来,为了能够让用户体验更好一些,我们需要长按的时候也不保存图片。那该如何实现呢?下面给出3种解决方案。使用 pointer-events:none、全局属性、加一层遮罩层
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!