canvas还能这么用?🤨 图片压缩70%

您所在的位置:网站首页 压缩文件后体积不变 canvas还能这么用?🤨 图片压缩70%

canvas还能这么用?🤨 图片压缩70%

2023-12-27 13:43| 来源: 网络整理| 查看: 265

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

图片上传功能在日常的开发中并不少见,但是图片的体积过大会增大服务器压力,用户体验感也不好,本文将基于canvas实现图片的压缩。全文无尿点,可以放心观看。

前言✨

之前和室友做了一个Vue3的蛋糕售卖和后台管理的系统,后台服务涉及到了管理员图片的上传,最开始采用base64直接上传,服务器响应时间太长(之前没经验),后来可以选择直接传输file类型的文件,但是管理员上传的文件大小不受控制,文件较大的时候,服务器响应时间也比较久。摸爬滚打的参考了很多的文章之后,也踩了不少的坑,于此记录下来,分享技术的同时。也算是自己的一个小总结。

前置知识✨

主要简单介绍一下后续所使用到的对象/方法,尽量用最简单最浅显的语言来讲清楚这部分知识,可放心食用。

1.FileReader对象 MDN-FileReader

通过FileReader对象可以读取文件/缓冲区内容

FileReader.readAsDataURL 方法会读取指定的 Blob 或 File 对象

FileReader.onload: 读取完毕之后的回调函数,返回的数据类型长这样(先接着往下看)

image.png

2.canvas MDN-canvas

可以理解为一个标签,可以通过这个标签上的属性来绘制图片并且可以实现压缩效果。

canvas.getContext('2d'): 获得渲染上下文和它的2d绘画功能,返回ctx对象 ctx.drawImage(img,x,y,width,height): 绘制图像 | x,y对应的是坐标轴,width、height绘制的图片大小 canvas.toDataUrl(type, encoderOptions): 返回一个包含图片展示的 data URI var canvas = document.getElementById("canvas"); var dataURL = canvas.toDataURL(); console.log(dataURL); // 注意返回结果中的逗号(伏笔) // "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby // blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"

type: 指定图片的类型 | encoderOptions(可选) 指定图片体积的压缩比(0~1)默认为0.92

本文的重点就是基于encoderOptions进行压缩图片

3.atob MDN-atob

对经过 base-64 编码的字符串进行解码。

let encodedData = window.btoa("Hello, world"); // 编码 SGVsbG8sIHdvcmxk let decodedData = window.atob(encodedData); // 解码 Hello, world 4.ArrayBuffer MDN-ArrayBuffer

ArrayBuffer对象代表储存二进制数据的一段内存

const buf = new ArrayBuffer(8);

上面代码生成了一段 8 字节的内存区域,每个字节的值默认都是 0。(看不太懂,不着急👇接着往下看)

5.File 构造函数 MDN-File var myFile = new File(bits, name[, options]);

bits: 一个包含ArrayBuffer,ArrayBufferView,Blob,或者 DOMString 对象的 Array — 或者任何这些对象的组合。这是 UTF-8 编码的文件内容。

name: 表示文件名称,或者文件路径。

options: 选项对象,包含文件的可选属性。

正文✨ 1. 搭建一个基于koa的后台服务|接受图片并且返回 图片的url地址

如果不了解koa的童鞋,可以讲这段代码拷贝,安装一下对应的依赖启动服务即可。

koa-router 后端路由服务 koa-static 开启静态服务 koa-body 读取file文件 const path = require('path') const Koa = require('koa') const KoaStatic = require('koa-static') const Router = require('koa-router') const koaBody = require('koa-body') const router = new Router() const app = new Koa() app.use(koaBody({ multipart: true,// 支持请求中body携带文件类型的数据 formidable: { uploadDir: path.join(__dirname, '/public/uploads'),//设置图片保存地址 keepExtensions: true } })) // 接受图片保存在public文件夹下,开启静态web服务 app.use(KoaStatic(path.join(__dirname, '/public'))) // 上传图片中间件 function uploadImg (ctx) { const file = ctx.request.files.file const basename = path.basename(file.path) // 根据图片的绝对地址获取图片名称: 例如 /d/aa/c.js -> c.js ctx.body = { url: `${ctx.origin}/uploads/${basename}` } } // cors 跨域解决方案 app.use(async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', '*'); if (ctx.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } }) router.post('/uploads', uploadImg) app.use(router.routes()) app.listen(9001, () => console.log('服务启动成功')) // http://localhost:9001/uploads

目录结构:

image.png

2.上传图片,返回图片的url地址之后渲染到页面中(模拟一次正常的网络交互) 提交表单

First make it work,then make it fast 我们先让流程跑通

const input = document.querySelector('input') const img = document.getElementById('img') const btn = document.querySelector('button') const formData = new FormData() function reqUploadImg() { fetch('http://localhost:9001/uploads', { method: 'post', body: formData }).then(res => res.json()) .then(res => { console.log(res) img.src = res.url }) } input.onchange = function (e) { const file = e.target.files[0] formData.append('file', file) }

上面的代码需要注意几个内容(正常交互不必在意)

开启liveServer 提交表单文件FormData 添加表单内容为 append 方法(我找了好久。。。用set方法一直为空) 不能讲Service文件夹和前台代码放在一个根目录下,否则每次提交会刷新,原因是liveServer的监视,如下 可以看到,每次提交之后因为koa后台服务保存了图片之后,进而被liveServer监视,导致浏览器错以为代码发生改变而自动刷新了

12ea546d4c3331273658a13d07d1ee19 (1).gif

展示效果:

QQ录屏20220226221408.gif

3.优化代码

写代码之前先捋清楚我们的思路

1.首先我们需要读取图片 2.读取完文件之后通过canvas压缩生成一个新的图片 3.再通过buffer重新转化为图片 先看看效果,打印两次文件的size大小,从158kb到52kb,体积变为原来的1/3不到

image.png

修改后的代码(后面会讲base64转化原理)

input.onchange = function (e) { const file = e.target.files[0] // console.log(file) compressPic(file).then(resultFile => { formData.append('file', resultFile) }) } function compressPic(file, encoderOtp = 0.2) { return new Promise(resolve => { // 1. 通过FileReader读取文件 const reader = new FileReader() let res = reader.readAsDataURL(file) reader.onload = (event) => { // 2. 读取完毕之后获取图片的base64(上文伏笔),并创建新图片 const { result: src } = event.target const image = new Image() image.src = src image.onload = () => { // 3.图片加载完之后通过canvas压缩图片 const canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height // 3.1 绘制canvas const ctx = canvas.getContext('2d') ctx.drawImage(image, 0, 0, image.width, image.height) // 3.2 返回图片url地址,并且进行压缩 const canvasURL = canvas.toDataURL(file.type, encoderOtp) const buffer = atob(canvasURL.split(',')[1]) // 3.3 bufferArray 无符号位字节数组 相当于在内存中开辟length长度的字节空间 let length = buffer.length const bufferArray = new Uint8Array(length) // 3.4 给新开辟的bufferArray赋值 while (length--) { bufferArray[length] = buffer.charCodeAt(length) } // 3.5将压缩后的文件通过resolve返回出去 const resultFile = new File([bufferArray], file.name, { type: file.type }) console.log(resultFile) resolve(resultFile) } } }) } 4.测试结果

我们再测试其他的几张图片试一试

文件格式原体积encoder0.2encoder0.3encoder0.4encoder0.5默认值0.92jng158kb51kb66kb75kb87kb190kbgif490kb55kb72kb85kb99kb410kbpng118kb141kb141kb141kb141kb141kbother76kb38kb47kb50kb60kb100kb

结论 -----这部分结论可以参考 :# 前端图片最优化压缩方案

encoder系数在0.2-0.5之间可以有效的压缩图片体积 encoder默认值体积反而会变大 png格式文件,和encdoer系数无关,甚至体积不减反增 这里就展示了0.2~0.5的数据 低于0.2会出现图片压缩过度,质量下降。

左图为encoder0.1 右图为0.2,当encoder为0.1的时候,仔细看看还是可以看出丢失的精度。

5.发现问题

注意看第三行测试用例,为什么无论encoder是什么值,压缩后的体积都是一样呢? 🤔🤔

看来这其中一定哪个环节出了问题,继续测试发现只要是png格式的文件就会出现这样的情况,其他文件即便是gif也会进行压缩

我们的结论一定正确吗?

再来看看这张png格式文件

image.png

再试试体积很小的svg文件

image.png

经过多组测试发现,使用canvas压缩体积的时候,文件体积过小反而会出现越压缩体积越大的情况

6.解决小体积图片压缩问题

那这样就很好解决了,设置一个体积阀门,小于这个体积的图片我们不做限制。毕竟我们的初心就是为了压缩大体积文件

在上述代码的基础上添加 minSize,当小于300kb的时候不进行canvas压缩

image.png 这里其实也可以通过Promise.all实现多文件的压缩。这里就不演示了

base64转换原理

说出来也挺巧的,在写这篇文章的时候恰好看到大佬山月在朋友圈发的base64转换工具,感觉很棒!这里我们就借助这个工具来和大家讲讲base64的转换原理。地址:base64转换工具

image.png

转换流程:

将数字/字母转化为 Asc编码、汉字对应16位两字节的方式 将AsCii编码转化为base64编码。编码的方式为: 每 3*8bit 的字节转换为 4*6bit 的字节,剩下的两位用 00 补齐,所以Base64 编码后的数据比编码之前大 1/3 每不足三个字节,自动补全,结果采用=占位 如👇输入字母a 转化为 YQ==

image.png

总结✨

image.png

基于canvas可以实现对大图片的体积压缩 | 甚至可以实现指定drawImage图片宽高,进一步缩小图片体积 图片体积过小大概小于 150kb以下,会出现越压缩体积越小的情况 (这部分没有太多数据支撑,可能会存在一定误差) 基于binary-to-text算法,可以将二进制数据转化为64格式,但是体积也会增大1/3

感谢看完,如果对你有帮助的话,点个👍再走呀~ 倔友😁



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3