JavaScript純前端實(shí)現(xiàn)在線(xiàn)GIF壓縮
前言
原因是我在寫(xiě)公眾號(hào)文章的時(shí)候,公眾號(hào)編輯器只能上傳最大10M的GIF,而我剛好需要上傳的GIF超過(guò)了這個(gè)閾值。
所以我就隨便搜了一個(gè)壓縮GIF的工具,有一些壓縮完了下載需要付費(fèi),有一些不付費(fèi)就只能壓縮低于某個(gè)閾值的文件。
不過(guò)也能理解吧,畢竟別人也是需要賺錢(qián)的,這種文件的處理如果放在服務(wù)端做,那必然是要耗費(fèi)不少資源的,如果放在純前端做,又不能保證文件處理的速度。
然后我就想著GIF壓縮應(yīng)該不難吧,應(yīng)該有現(xiàn)成的工具庫(kù)吧,于是我就開(kāi)始動(dòng)手去實(shí)現(xiàn)一個(gè)GIF壓縮工具。這里我很執(zhí)拗地要去實(shí)現(xiàn)一個(gè)純客戶(hù)端的GIF壓縮(也不知道為啥我這么執(zhí)拗。)
初探GIF壓縮
我們都知道GIF是一張張靜態(tài)的圖片播放形成的動(dòng)圖,所以我一開(kāi)始就想著,如果我有一個(gè)庫(kù),能幫我把GIF所有幀都抽取出來(lái),然后我對(duì)所有幀的圖片進(jìn)行一個(gè)縮放的有損壓縮,壓縮完之后再把所有圖片合成一個(gè)新的GIF,那么我不就把GIF給壓縮了么。
然后我就找到了gif.js和omggif.js這兩個(gè)庫(kù):
omggif負(fù)責(zé)解析GIF,獲取GIF的每一幀圖像- 把獲取到的每一幀圖像進(jìn)行處理了之后,使用
gif.js合成GIF
那么可以寫(xiě)出下面的代碼,以下的代碼就是一個(gè)分離GIF幀+合成GIF的代碼,還沒(méi)有加上壓縮每一幀的邏輯:
fetch("/test.gif")
.then((response) => response.arrayBuffer())
.then((buffer) => {
// 用omggif解析GIF
let reader = new omggif.GifReader(new Uint8Array(buffer));
// 創(chuàng)建新的gif.js實(shí)例
let gif = new GIF({
workers: 2,
quality: 5,
width: reader.width,
height: reader.height,
});
const width = reader.width;
const height = reader.height;
// 創(chuàng)建canvas元素
const imgs = [];
// 遍歷所有的frames,并添加到gif.js實(shí)例中
for (let i = 0; i < reader.numFrames(); i++) {
let frameInfo = reader.frameInfo(i);
let canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d", { willReadFrequently: true });
let imageData = ctx.createImageData(width, height);
reader.decodeAndBlitFrameRGBA(i, imageData.data);
ctx.putImageData(imageData, 0, 0);
gif.addFrame(ctx, { delay: frameInfo.delay });
imgs.push(canvas.toDataURL());
}
//預(yù)覽分離出來(lái)的幀
const div = document.createElement("div");
imgs.forEach((url) => {
const img = document.createElement("img");
img.src = url;
div.appendChild(img);
});
document.body.appendChild(div);
// 生成GIF并下載
gif.on("finished", function (blob) {
console.log("finish");
let url = URL.createObjectURL(blob);
document.querySelector("#img").src = url;
const a = document.createElement("a");
a.download = true;
a.href = url;
a.click();
});
gif.render();
});
但是我分離幀之后發(fā)現(xiàn),分離出來(lái)的幀相當(dāng)奇怪:

而且合并之后的GIF感覺(jué)完全被破壞掉了,我不清楚是不是我的這種思路本身就是不可行的,還是說(shuō)這兩個(gè)庫(kù)不能這么用。是不是我這樣做了之后導(dǎo)致描述GIF的信息丟了?(比如調(diào)色盤(pán)或者其他的一些全局信息,評(píng)論區(qū)有大神可以指導(dǎo)一下么)

這種方式不可行之后,我又嘗試了一下別的方式。這一次我不再寄希望于純js實(shí)現(xiàn)的庫(kù),而是往wasm方向去探索。
然后我嘗試使用Rust去解析GIF,并返回所有解析后的圖片給前端,前端再去做有損壓縮,但這一做法耗時(shí)太長(zhǎng),也不是成功的嘗試,所以代碼就不放出來(lái)了。
接著我就想到了ffmpeg,我知道它有壓縮GIF的功能,而且他也有成熟的wasm版本,可以便捷地在前端引入使用,但是它壓縮后近10M的體積依舊讓人望而生畏。
gifsicle
在繼續(xù)搜索GIF壓縮等關(guān)鍵詞時(shí),發(fā)現(xiàn)了gifsicle這個(gè)庫(kù)。它是一個(gè)用于處理GIF圖像的命令行工具和庫(kù),提供了很多功能包括創(chuàng)建、編輯、優(yōu)化和調(diào)整GIF。
與ffmpeg對(duì)比起來(lái),它是一個(gè)更專(zhuān)注于GIF處理的庫(kù),所以體積會(huì)比ffmpeg肯定小不少,所以我就想著能不能弄一個(gè)它的wasm版本移植到瀏覽器中使用。
后面還真讓我找到了它對(duì)應(yīng)的wasm版本,具體鏈接可以查看:gifsicle-wasm-browse,這個(gè)庫(kù)GZip壓縮之后只有150K左右,完全不用擔(dān)心體積問(wèn)題。
使用gifsicle的時(shí)候,常常使用如下的手段去減少GIF的體積:
-O參數(shù):
- O1:該選項(xiàng)對(duì)應(yīng)于輕度優(yōu)化,它會(huì)執(zhí)行一些基本的優(yōu)化步驟,刪除無(wú)用的圖像數(shù)據(jù)和元數(shù)據(jù),以減小文件大小。這個(gè)級(jí)別的優(yōu)化通常執(zhí)行較快,但可能不會(huì)最大限度地減小文件大小。
- O2:中度優(yōu)化比輕度優(yōu)化更深入,它可能會(huì)花費(fèi)更多的時(shí)間來(lái)尋找更多的優(yōu)化機(jī)會(huì)。這通常會(huì)導(dǎo)致更好的壓縮比和更小的文件大小。
- O3:最大優(yōu)化級(jí)別會(huì)執(zhí)行最深入的優(yōu)化,可能需要更多的時(shí)間來(lái)完成,但通常會(huì)實(shí)現(xiàn)最大的文件大小減小。這個(gè)級(jí)別的優(yōu)化可能對(duì)CPU和內(nèi)存的需求更高。
--loosy:
--lossy會(huì)減少圖像中使用的顏色數(shù)量,從而減小顏色表的大小。這會(huì)導(dǎo)致顏色的近似和失真,因?yàn)樵嫉念伾畔?huì)被近似為顏色表中的顏色。- 高
--lossy程度下,一些微小的圖像細(xì)節(jié)可能會(huì)被去除,以進(jìn)一步減小文件大小。這可能導(dǎo)致圖像的一些細(xì)節(jié)在視覺(jué)上的損失
減少顏色數(shù):
GIF中每個(gè)像素可以使用圖像調(diào)色板中的一種顏色,而顏色數(shù)量表示這些不同顏色的總數(shù)。GIF使用的顏色調(diào)色板通常是8位色,允許最多256種不同的顏色。- 減少GIF的顏色數(shù)量可以降低圖像的文件大小,因?yàn)閳D像中使用的顏色越少,每個(gè)像素的顏色信息所需的位數(shù)就越少。然而,減少顏色數(shù)量也可能導(dǎo)致圖像的視覺(jué)質(zhì)量下降,尤其是對(duì)于包含大量顏色細(xì)節(jié)的圖像。
刪除注釋和元數(shù)據(jù)
裁剪和縮?。鹤钪庇^的壓縮手段,屬于有損壓縮
調(diào)整幀速率:相當(dāng)于減少組成這個(gè)GIF文件的圖片數(shù)量,也是有損壓縮
具體實(shí)現(xiàn)
首先安裝gifsicle-wasm-browser這個(gè)包,然后簡(jiǎn)單搭建一個(gè)表單如下:

gifsicle的具體文檔,可以點(diǎn)擊這里查看。
在前端中可以參照以下方式來(lái)使用gifsicle:
gifsicle
.run({
input: [
{
file: buffer,
name: "input.gif",
},
],
command: [command],
})
.then(async (res) => {
});
在GIF的壓縮過(guò)程中,常常會(huì)把多種壓縮方式結(jié)合起來(lái)使用。如果單純的想壓縮GIF的體積而不考慮GIF的質(zhì)量,那就直接使用縮放進(jìn)行有損壓縮就好了;如果想保留GIF的質(zhì)量的同時(shí)壓縮GIF的體積,那還是需要多方參數(shù)的組合嘗試。
壓縮前的圖像:

以下是我一些嘗試壓縮的參數(shù)以及壓縮前后的對(duì)比:
壓縮參數(shù): -O2 --lossy=180 input.gif --colors 64 --scale 0.8 -o /out/out.gif
- 壓縮后:體積:4.0MB,顏色數(shù)量:64,幀數(shù)不變,分辨率不變
- 執(zhí)行時(shí)間:72s

壓縮參數(shù):-O2 --lossy=180 input.gif --colors 32 --scale 0.8 -o /out/out.gif
- 壓縮后:體積:3MB,顏色數(shù)量:32,幀數(shù)不變,分辨率不變
- 執(zhí)行時(shí)間:60s
- 可見(jiàn)圖像顏色已經(jīng)嚴(yán)重變形

壓縮參數(shù):input.gif --colors 64 --scale 0.5 -o /out/out.gif
- 壓縮后:體積:3MB,顏色數(shù)量:64,幀數(shù)不變,分辨率變?yōu)橹耙话?/li>
- 執(zhí)行時(shí)間:19s
- 圖像較為模糊,顏色也有少許變形

可以看到在大多數(shù)的壓縮參數(shù)下,都需要較長(zhǎng)的執(zhí)行時(shí)間,這是因?yàn)檫@個(gè)wasm包還沒(méi)有實(shí)現(xiàn)多核處理,還沒(méi)辦法利用多核CPU的優(yōu)勢(shì),所以處理起來(lái)時(shí)間會(huì)比較長(zhǎng)。

總結(jié)
如果這個(gè)包實(shí)現(xiàn)了多核處理,執(zhí)行時(shí)間能縮短一些的話(huà),那么我覺(jué)得會(huì)有更多的GIF處理會(huì)放在前端來(lái)做。
我一直覺(jué)得純前端處理是一件很酷的事情,對(duì)于開(kāi)發(fā)者來(lái)說(shuō),無(wú)需承擔(dān)昂貴的計(jì)算資源成本,如果不想承擔(dān)服務(wù)器成本,這個(gè)時(shí)候還有一種解決方法就是做成桌面應(yīng)用,把任務(wù)處理的二進(jìn)制文件打包下載到用戶(hù)本地。
但如果是純前端實(shí)現(xiàn)的話(huà),對(duì)于使用者來(lái)說(shuō),無(wú)需下載任何東西,點(diǎn)開(kāi)即用。
所以后面也理解了網(wǎng)上一些壓縮GIF需要收費(fèi)的工具,一個(gè)好的在線(xiàn)GIF壓縮產(chǎn)品它能同時(shí)保證執(zhí)行速度、壓縮體積、壓縮質(zhì)量,這還是相當(dāng)不容易的。
以上就是JavaScript純前端實(shí)現(xiàn)在線(xiàn)GIF壓縮的詳細(xì)內(nèi)容,更多關(guān)于JavaScript GIF壓縮的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
jQuery實(shí)現(xiàn)文字自動(dòng)橫移
本文詳細(xì)介紹了通過(guò)jquery尺寸相關(guān)函數(shù)實(shí)現(xiàn)文字自動(dòng)橫移的方法。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01
JS的運(yùn)行機(jī)制之事件循環(huán)機(jī)制示例詳解
事件循環(huán)是JavaScript實(shí)現(xiàn)異步編程的核心,通過(guò)單線(xiàn)程執(zhí)行模型協(xié)調(diào)同步代碼、回調(diào)隊(duì)列、異步事件及微任務(wù)和宏任務(wù),這篇文章主要介紹了JS運(yùn)行機(jī)制之事件循環(huán)機(jī)制的相關(guān)資料,需要的朋友可以參考下2025-07-07
微信小程序:報(bào)錯(cuò)(in promise) MiniProgramError
這篇文章主要介紹了微信小程序:報(bào)錯(cuò)(in promise) MiniProgramError,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
tsc性能優(yōu)化Project References使用詳解
這篇文章主要為大家介紹了tsc性能優(yōu)化Project References使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
微信小程序?qū)崿F(xiàn)點(diǎn)餐小程序左側(cè)滑動(dòng)菜單
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)點(diǎn)餐小程序左側(cè)滑動(dòng)菜單,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
JS實(shí)現(xiàn)靈巧的下拉導(dǎo)航效果代碼
這篇文章主要介紹了JS實(shí)現(xiàn)靈巧的下拉導(dǎo)航效果代碼,通過(guò)自定義js插件腳本dropdown.js實(shí)現(xiàn)簡(jiǎn)單的下拉導(dǎo)航效果的功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
JavaScript動(dòng)態(tài)代理的各種用法詳解
動(dòng)態(tài)代理是ES6引入的強(qiáng)大功能,通過(guò)Proxy對(duì)象實(shí)現(xiàn),允許你攔截和自定義對(duì)目標(biāo)對(duì)象的操作,下面我將全面介紹JavaScript動(dòng)態(tài)代理的各種用法,需要的朋友可以參考下2025-09-09
基于Bootstrap實(shí)現(xiàn)tab標(biāo)簽切換效果
這篇文章主要為大家詳細(xì)介紹了基于Bootstrap實(shí)現(xiàn)tab標(biāo)簽切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05

