前情提要
页面打印导出pdf文件的时候,图片大小会影响pdf文件大小。
为了减小pdf文件大小,需要将图片压缩一下。在只有图片地址的情况下,将图片压缩后显示,一开始用的browser-image-compression插件,这是js压缩,是个异步函数,速度有点慢,于是大佬提出用canvas压缩,一番百度之后,抽出了一个基础版。
不考虑代码优雅问题,只说能用就行。
1 代码
const imgUrl = "图片地址"
// 创建一个img标签
const img = new Image();
// 先给img加上onload方法,再设置src,防止设置onload的时候,已经加载好图片,onload就一直不触发
img.onload = () => {
// 创建canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置canvas宽高,具体设置多少,后面解释
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// 在canvas绘制前填充白色背景。
// 遇到过白底的证件照压缩之后背景色变成了黑底,所以加一步填充背景色,不加也不影响压缩。
ctx.fillStyle = "#fff";
// 绘制图片
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// canvas转成图片地址,在这一步进行文件大小压缩,第二个参数设置图片质量,不写默认0.92,后面解释为什么写0.2
const base64 = canvas.toDataURL('image/jpeg', 0.2);
// 拿到新的图片地址后就能用来显示了,如何传给img标签大家就各显神通吧,后面会给一种方案
}
img.src = imgUrl;
示例图片:4000 × 2472 px 6.8M
页面显示:162 × 100 px
页面放大150%截图
2 canvas的宽高
canvas的宽高设置会影响最终图片的质量(模糊度和大小两方面)。
一般来说,原图尺寸 >> 页面显示的缩略图,原图几百几千px的宽高,放到页面上可能就被缩小到几十px,所以canvas大小可以从原图到缩略图,取值范围非常广。
(在toDataURL第二个参数默认0.92的情况下)
设置原图的尺寸,压缩效果有点差,但是图片比较清晰;
设置缩略图尺寸,压缩效果较好,但是图片模糊;
考虑到导出pdf之后,图片也就指甲盖大小,图片糊点就糊点吧,减小pdf大小最重要。但是我也不想让图片糊成马赛克,毕竟实际的缩略图尺寸真的太小了。所以我用的是缩略图宽高*4的尺寸。
3 toDataURL第二个参数
toDataURL第二个参数设置也会影响最终图片的质量(模糊度和大小两方面)。
quality越大,图片的质量越高(图片越清晰,文件大小也越大),取值范围是0~1.0之间的double值。
网上看到一种说法是设置成0.2-0.5之间可以有效的压缩图片体积,减小pdf大小最重要,于是我用了0.2。
3 toDataURL第一个参数
第一个参数指定了图像的类型,例如"image/jpeg"或者"image/png",如果不指定,则默认使用"image/png"。
(网上查到)toDataURL第二个参数只适用于image/jpeg 或 image/webp类型的图片,他表示图像的显示质量,所以没管图片类型,toDataURL第一个参数都是"image/jpeg"。
试了下"image/jpg"和"image/png",同一张图,原图尺寸的情况下,压缩文件变大了,如果使用缩略图尺寸,那倒是压缩了,毕竟图片是真变小了,但效果没有"image/jpeg"好。
(至于原因,有大佬补充吗?)
4 canvas.toDataURL()之后图片大小增加
如果将canvas设置为缩略图尺寸,应该是不会遇到标题中的情况的,毕竟图片尺寸小了那么多,文件大小应该不至于变大(个人见解)
但如果canvas使用原图尺寸,会比较容易遇上压缩之后文件还变大了的情况。
(网上看到)有种解释是文件体积过小反而会出现越压缩体积越大,这种情况可以设置一个体积阀门,小于某个尺寸(比如300K)的时候不进行压缩。
虽然最后没用上这个方案,但是测试的时候遇到过这个问题,所以记一下,如何根据图片url获取到图片大小。
5 本地验证遇到了跨域问题
测试canvas压缩图片可行性的时候,用的本地图片,结果遇到跨域问题
Access to image at ‘file://xxx/xxx.jpg’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.
4.1 查了下,可以改成使用图床。
随便找的免注册图床,https://wmimg.com/
这个图床的貌似有点问题,4.93M的图片上传之后变成6.8M,不过测试canvas压缩效果倒是比较明显。
这个图床也是免注册,https://postimages.org/
但是它自带的图片压缩功能,把4.93M的图片压成了33.8k,它都压那么好了我还能压什么。
4.2 然后,问题变成另一个报错。
Uncaught DOMException: Failed to execute ‘toDataURL’ on ‘HTMLCanvasElement’: Tainted canvases may not be exported.at img.onload ()
(网上查到)这是由于drawImage()向canvas导入的图片跨域而导致的。
这个好解决,给img加一下crossOrigin属性,打开跨域资源允许权限
const img = new Image();
img.crossOrigin = "Anonymous";
如果加上了crossOrigin = "Anonymous"依然存在跨域问题,那就换个图床。
因为存放图片地址的服务器也要开启跨域允许权限,改不了服务器,那就换个服务器吧。