利用canvas压缩图片

前情提要

页面打印导出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 × 2472px 6.8M
页面显示:162 × 100px
页面放大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 代码补充

实际应用的时候,需要批量压缩图片

const waitImgLoad = () => {
    // getElementsByTagName获取所有图片信息
    const imgList = document.getElementsByTagName("img");
    // 因为需要等待所有图片压缩完 && 重新渲染完,再导出pdf,所以用promise记录进度
    let promiseAll = [];
    for (let i = 0; i < imgList.length; i++) {
        // 一张图片一个promise
        promiseAll[i] = new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                compressImg(img, imgList[i], resolve);
            }
            // img加载失败的处理
            img.onerror = () => {
                // 这里写压缩失败的处理
                // ...
                resolve(true);
            }
            img.src = imgList[i].src;
        })
    }
    // 没有图片,不用考虑加载图片问题
    if (promiseAll.length == 0) {
        // 这里需要给个加载完成的提示
    }
    // 等所有图片加载完成后,结束等待状态
    Promise.all(promiseAll).then((img) => {
        // 这里需要给个加载完成的提示
    })
}
// canvas压缩图片
function compressImg(img, imgEle, resolve) {
    // 生成canvas画布
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    // 设置canvas宽高为缩略图宽高 * 4
    canvas.width = imgEle.width * 4;
    canvas.height = imgEle.height * 4;
    // 在canvas绘制前填充白色背景
    ctx.fillStyle = "#fff";
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    // canvas转成图片地址,在这一步进行文件大小压缩
    const base64 = canvas.toDataURL('image/jpeg', 0.2);
    // 将新的图片地址传给img标签,并且监听加载进度
    imgEle.addEventListener("load", () => {
        resolve(true)
    }, false);
    imgEle.src = base64;
}

6 本地验证遇到了跨域问题

测试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.

6.1 查了下,可以改成使用图床。

随便找的免注册图床,https://wmimg.com/
这个图床的貌似有点问题,4.93M的图片上传之后变成6.8M,不过测试canvas压缩效果倒是比较明显。
这个图床也是免注册,https://postimages.org/
但是它自带的图片压缩功能,把4.93M的图片压成了33.8k,它都压那么好了我还能压什么。

6.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"依然存在跨域问题,那就换个图床。
因为存放图片地址的服务器也要开启跨域允许权限,改不了服务器,那就换个服务器吧。

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值