假如说你做的网站有需要用户自定义上传图片的功能,那么往往这些图片,都需要进行压缩,且往往是需要经过服务器端压缩的。同时如果说你不懂图片压缩原理的话,你还得去花钱使用他人的API进行图片压缩(例如tinyPng),熊猫压缩的服务器部署在国外,我们调用其API也存在响应速度慢、丢包等问题。
这时候有人会说,canvas压缩不就完事了吗?的确,使用canvas也能够进行图片压缩,但是博主上手操作之后发现其效果有点糟糕。假如说画布大小与图片大小一致,调低quantity的话,压缩完的图片往往会很模糊;而如果画布大小放大为图片宽高的两倍,图片压缩的效果却不尽人意。
博主原本也通过找API调用来做图片压缩功能,但是市面上大多有提供API功能的图片压缩网站,符合要求的大多需要收费,博主在找的过程中,发现了有几个压缩网站,压缩是不经过后端服务的,也就是前端浏览器来做压缩,且其中的一个网站是开源项目(Squoosh谷歌图片压缩工具),博主灵机一动,想着要不就把人家做好的搬过来借用一下。好了,废话少说这就开始行动!
该项目是使用TypeScript
开发,由于博主使用的项目中没有使用到TypeScript
,故找出相应的方法后,需要将其转为JS
才能用到博主的项目中。
网站首页如下:
通过select an image按钮上传一张图片后,就会进入图片压缩前后的对比界面(前端压缩速度很快,几乎可以说是秒压),如下图:
页面左下角和有下角分别为压缩前后图片大小对比,右下角可以通过Quality滑动条来控制图片的压缩质量(默认75),中轴线可以拖拽查看图片压缩前后的效果图。速度快,可调节质量,可以说这就是博主想要的效果,10k+star的项目就是屌!
博主一番分析后,决定从Quality滑动条入手,找出其背后调用的方法:
首先该项目为preact
项目(github的star目前为26.9K),写法跟react
差不多,是react
的轻量级实现,其简介就一句话:“Fast 3kB alternative to React with the same modern API.”,看来博主又多了一个学习目标了噗。
博主作为TypeScript
新手,React
菜鸟级别选手,定位到了滑动条的change事件的定义位于src/codecs/mozjpeg/options.tsx第27行,其事件最后会调用this.props.onChange(newOptions)
;调用父组件Options
的onChange
事件位于src/components/Options/index.tsx第124行;然后它是作为传入的prods事件去调用其父组件Compress
的onEncoderOptionsChange
私有函数做setState
操作,位于src\components\compress\index.tsx第484行。
在setState
之后,会触发组件内的私有函数updateImage
,其会去调用compressImage
方法去判断解码类型数据,从而执行相应的压缩方法,最终会执行137行的case,case mozJPEG.type: return processor.mozjpegEncode(image, encodeData.options);
。
到此,才剖析到了真正的压缩方法Processor
类中的mozjpegEncode
方法,位于src\codecs\processor.ts第139行,接下来只需要全局搜索mozjpegEncode
方法,按照上边一步步地往里面剖析,将相关联的代码抽离出来放到一个文件夹中,结果如下图:
由于博主写的项目中并没有引入TS,同时也不希望因为这个压缩功能而导致项目中混入TS,故博主去除了.d.ts结尾的类型声明文件,再将其编译为JS文件,然后再修改每个文件引用的地址。
接着在该文件夹中新建一个index.js
作为该方法的入口文件,对外暴露一个compressImg
方法接收饿了么UI上传组件
before-upload
事件回调中的file
,返回一个压缩成功后的compressFile
,代码如下:
import { encode } from './encoder'
// 默认参数配置
let option = {
quality: 75, // 可调整压缩质量(默认75)
baseline: false,
arithmetic: false,
progressive: true,
optimize_coding: true,
smoothing: 0,
color_space: 3,
quant_table: 3,
trellis_multipass: false,
trellis_opt_zero: false,
trellis_opt_table: false,
trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75
}
export async function compressImg(file) {
// 接收file之后需取到宽高,利用canvas绘制得到imageData
let img = new Image()
let _URL = window.URL || window.webkitURL
img.src = _URL.createObjectURL(file)
let canvas = document.createElement('canvas')
return new Promise((resolve) => {
img.onload = async () => {
canvas.height = img.height
canvas.width = img.width
let ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
let imageData = ctx.getImageData(0, 0, img.width, img.height)
// 调用Squoosh中暴露出来的压缩方法encode
let result = await encode(imageData, option)
// 其返回结果为ArrayBuffer类型,将其转为blob类型后再转回file返回到页面中
let blob = new Blob([result])
let compressFile = new File([blob], file.name, {
type: file.type
})
resolve(compressFile)
}
})
}
到这里就结束了吗?并没有哦,抽离出来的文件中,有一个.wasm
结尾的文件,这个wasm
文件就相当于windows的dll库,只不过是web上不是windows上的,其压缩是依赖于这个文件的。
但是在前端中并不认识他,需要在对应的webpack配置中利用file-loader
或者wasm-loader
去解析它(这里请自行配置)。
终于到了看成果的时候了,不枉博主费尽心思地借鉴,效果还是挺给力的,不多说了直接上图:
博主上传了一张494KB(size为50W+),压缩完毕后的图片约为100多KB(size为10W+),可以非常明显得看到效果还是非常的明显的,而且压缩的速度非常的快!这里的压缩效果其实跟Squoosh一致,所以小伙伴们想看效果也可以直接上其网站试试【传送门】。
本文章作为一篇没有什么技术含量的博文,不是教大家如何去从0做前端图片压缩,而是教的大家投机取巧做搬运工,博主确实心底有点惭愧。
但毕竟博主作为菜鸟前端选手,靠自己来手搓一个压缩图片方法不太现实,待日后博主了解其压缩原理,定会再开一篇博文,分享给大家如何从0搭建!好了,该滚去继续学习了,还是太嫩了。