纯前端实现 JPG 图片压缩 | canvas
在线 Demo 体验地址 →: https://demos.sugarat.top/pages/jpg-compress/
前言
在迭代图床应用时,需要用到图片压缩,在之前分享了使用 UPNG.js 压缩 PNG 图片,这里记录分享一下如何处理 JPG 图片。
搜罗调研了一圈,JPG 图片的处理,基本都是围绕 canvas 展开的。
- 掘金:前端实现图片压缩技术调研
- 相关开源库(近期还有迭代维护的):Compressor.js,browser-image-compression。
如何判断图片是 JPG
同样第一步当然是判断图片类型,不然就没法正常的做后续处理了。
搜索了解了一下,JPG 图片的前三字节是固定的(16进制表示):FF D8 FF
。
下图是 VS Code 插件 Hex Editor 查看一个 JPG 图片的 16 进制表示信息。
于是可以根据这个特性判断,于是就有如下的判断代码。
function isJPG(file) {
// 提取前3个字节
const arraybuffer = await file.slice(0, 3).arrayBuffer()
// JPG 的前3字节16进制表示
const signature = [0xFF, 0xD8, 0xFF]
// 转为 8位无符号整数数组 方便对比
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
const source = new Uint8Array(arraybuffer)
// 逐个字节对比
return source.every((value, index) => value === signature[index])
}
当然社区也有现成的 is-jpg 库可以使用。
可看判断代码还是很简单的。
下面将先介绍一下上述两个开源库的简单用法与特色,最后再介绍一下直接使用 canvas API
压缩的方式以及注意事项。
Compressor.js
简介
JavaScript 图像压缩工具。使用浏览器原生的
canvas.toBlob API
实现压缩,有损压缩
,异步
,在不同的浏览器压缩效果有所出入。一般可以用来在上传之前在客户端预压缩图像。
官方示例站点:Compressor.js PlayGround
使用
支持 npm
和 cdn
两种引入方式。
npm 加载
# 安装依赖
npm install compressorjs
// 项目中引入使用
import Compressor from 'compressorjs'
cdn 加载
<!-- html head 中引入 -->
<script src='https://cdn.staticfile.net/compressorjs/1.2.1/compressor.min.js'></script>
<!-- 项目中直接使用 Compressor 即可 -->
简单使用方式如下
// file 是待压缩图片的文件对象
new Compressor(file, {
quality: 0.8,
success(result) {
// result 是压缩后的图片内容
}
})
其余的 option 选项可以参考官方文档,主要是尺寸大小,压缩质量效果,图片信息的保留等细节的调节。
简单封装
可以简单用 Promise
封装一下,使用更加方便。
async function compressJPGByCompressor(file, ops) {
return new Promise((resolve, reject) => {
new Compressor(file, {
...ops,
success(result) {
resolve(result)
},
error(err) {
reject(err)
}
})
})
}
当然这种不支持 Promise
的回调用法函数用 Promise.withResolvers 包装最合适不过了。
当然浏览器不支持这个API的话 需要引入 polyfill
才行(可以从 core-js
中引入,或自己简单实现一下)。
function compressJPGByCompressor(file, ops) {
const { promise, resolve, reject } = Promise.withResolvers()
new Compressor(file, {
...ops,
success(result) {
resolve(result)
},
error(err) {
reject(err)
}
})
return promise
}
browser-image-compression
简介
浏览器中实现图片压缩,通过降低分辨率或大小来压缩 jpeg、png、webp 和 bmp 图像;支持使用 Web Worker 实现多线程的非阻塞压缩。
官方示例站点:compression PlayGround
其中多线程压缩使用 OffscreenCanvas: 一个可以脱离屏幕渲染的 canvas 对象。在 web worker
环境也可工作。
使用
同样的也支持 npm
和 cdn
两种引入方式。
npm 加载
# 安装依赖
npm install browser-image-compression
// 项目中引入使用
import imageCompression from 'browser-image-compression'
cdn 加载
<!-- html head 中引入 -->
<script src="https://cdn.staticfile.net/browser-image-compression/2.0.2/browser-image-compression.min.js"></script>
<!-- 项目中直接使用 imageCompression 即可 -->
简单使用方式如下
imageCompression(file, {
// 设置压缩后的最大大小,单位是 MB(会根据目标自动调整图片质量或者尺寸)
maxSizeMB: 1,
// 如果希望通过百分比控制质量,只需简单计算一下即可
// maxSizeMB: Math.round(file.size / (1024 * 1024) * quality),
// 也可设置压缩后最大的宽或者高 (自动应用于图片中较长的那一边)
// maxWidthOrHeight: 1920,
}).then((result) => {
// result 为压缩后的结果
})
可以看出来使用非常简单:
- 调整尺寸就使用
maxWidthOrHeight
; - 保持原尺寸就调整
maxSizeMB
的值。
简单封装
function compressImageByImageCompression(file, options = {}) {
const { width, height, quality = 0.8, ...ops } = options
return window.imageCompression(file, {
maxSizeMB: Math.round(file.size / (1024 * 1024) * quality),
maxWidthOrHeight: width || height || undefined,
libURL: 'https://cdn.staticfile.net/browser-image-compression/2.0.2/browser-image-compression.js',
...ops
})
}
这样调用起来更加方便灵活。
注意事项
默认是开启的多线程压缩,会从 https://cdn.jsdelivr.net
拉取 worker 脚本。
如果存在网络原因访问不通畅,可以通过 options.libURL
替换为自定义的脚本位置,比如使用 Staticfile CDN 资源。
imageCompression(file, {
// ...省略其它配置
libURL: 'https://cdn.staticfile.net/browser-image-compression/2.0.2/browser-image-compression.js'
})
canvas api
主流的 JPG 纯前端压缩方案,基本都是借助 canvas 实现的,区别就在于边界场景是否考虑周全,配套的特性能否满足将使用的场景。
使用
先创建 Image
对象,获取图片的基本信息
下面是使用 URL.createObjectURL
创建资源链接的方式:
const img = new Image()
// 图片完成加载
img.onload = () => {
// 获取图片宽高
const { width, height } = img
// 后续就可以使用 canvas 进行进一步的压缩处理
}
img.src = URL.createObjectURL(file)
当然这里也可以用 FileReader
,此时代码看上去多2行(hhh)
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (event) {
img.src = event.target.result
}
紧接着就可以使用 canvas
进行图像的绘制(img 完成加载后)
// 创建 canvas 元素
const canvas = document.createElement('canvas')
// 获取画布的2D渲染上下文
const ctx = canvas.getContext('2d')
// 设置 canvas 的宽高与图片一致
canvas.width = img.width
canvas.height = img.height
// 在 canvas 上绘制图片(待绘制的图片,画布上的起始坐标,绘制的宽高)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
// 如果把元素插入到页面中,则可以看到 canvas 绘制的图片
// document.body.appendChild(canvas);
接下来最核心的就行调用 canvas.toDataURL(type, quality)
进行"压缩"了。
// 只需要设置图片格式,与图片质量 两个参数即可
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.8)
接下来就需要将 compressedDataUrl
转化为 blob
或者 file
对象。
DataUrl
格式如下。
data:image/jpeg;base64,XXXX
# 数据标识符:以"data:"开头
# MIME类型描述:指示数据的类型,"image/jpeg"表示JPEG图像
# 数据编码:以base64编码表示,"XXXX"是 base64 编码数据部分
咱们先把mimetype
,decodedData
这 2 部分提取出来
const [dataDescription, base64Data] = compressedDataUrl.split(',')
// 文件类型
const mimetype = dataDescription.match(/:(.*?);/)[1]
// 解码 base64 数据
const decodedData = atob(base64Data)
最后将解码的 base64
数据转成 file
即可。
let n = decodedData.length
// 创建等字节大小的 Uint8Array
const u8arr = new Uint8Array(n)
// 遍历赋值
while (n--) {
u8arr[n] = decodedData.charCodeAt(n)
}
// 通过 Uint8Array 创建 File 对象
const result = new File([u8arr], file.name, { type: mimetype })
简单封装
完整代码如下:
async function compressImageByCanvas(file, options = {}) {
const { quality } = options
let { width, height } = options
let _resolve, _reject
const promise = new Promise((resolve, reject) => {
_resolve = resolve
_reject = reject
})
const img = new Image()
img.onload = function () {
// 如果只指定了宽度或高度,则另一个按比例缩放
if (width && !height) {
height = Math.round(img.height * (width / img.width))
}
else if (!width && height) {
width = Math.round(img.width * (height / img.height))
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = width || img.width
canvas.height = height || img.height
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const compressedDataUrl = canvas.toDataURL('image/jpeg', quality)
_resolve(dataURItoFile(compressedDataUrl, file.name))
}
img.src = createObjectURL(file)
return promise
}
function dataURItoFile(dataURI, fileName) {
const [dataDescription, base64Data] = dataURI.split(',')
const mimetype = dataDescription.match(/:(.*?);/)[1]
const decodedData = atob(base64Data)
let n = decodedData.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = decodedData.charCodeAt(n)
}
return new File([u8arr], fileName, { type: mimetype })
}
兼容性问题
笔者并没有深入测试 canvas 压缩的兼容性问题,但从社区的几个前端处理 JPG 库里的 README 描述与 issues 等可以归纳出使用 canvas
处理时,需考虑下面几个方面的问题:
- 大小限制:详见 不同浏览器和设备上 canvas 大小限制;
- 信息保留:
EXIF
信息,正确识别与处理图片方向; - 设备兼容性:移动端设备浏览器定制内核相对多, 边界情况较多(相关 API 的支持程度,canvas 差异性表现)。
参考:browser-image-compression
, Compressor.js
, localResizeIMG
完整 demo
笔者将本节内容整理成了一个 Demo,可以直接在线体验。
在线 Demo 体验地址 →: https://demos.sugarat.top/pages/jpg-compress/
大概界面如下(可修改配置切换压缩方案,对比效果):
纯血 HTML/CSS/JS,复制粘贴就能运行。
完整源码见:GitHub:ATQQ/demos - jpg-compress
最后
后续将继续学习&探索一下 GIF
,MP4 转 GIF
等常用的动图前端处理实现的方式。
纯前端实现 JPG 图片压缩 | canvas的更多相关文章
- 使用HTML5的两个api,前端js完成图片压缩
主要用了两个html5的 API,一个file,一个canvas,压缩主要使用cnavas做的,file是读取文件,之后把压缩好的照片放入内存,最后内存转入表单下img.src,随着表单提交. 照片是 ...
- 利用HTML5,前端js实现图片压缩
http://blog.csdn.NET/qazwsx2345/article/details/21827553 主要用了两个HTML5的 API,一个file,一个canvas,压缩主要使用cnav ...
- HTML5时代的纯前端上传图片预览及严格图片格式验证函数(转载)
原文地址:http://www.2cto.com/kf/201401/274752.html 一.要解决什么样的问题? 在写这个函数之前,有们童鞋在群里问如何纯前端严格验证图片格式.这在html5时代 ...
- 图片纯前端JS压缩的实现
一.图片上传前端压缩的现实意义 对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大的提高了用户体验. 这种体验包括两方面: 由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅 ...
- 基于canvas的前端图片压缩
/*common*/ /** * canvas图片压缩 * @param {[Object]} opt [配置参数] * @param {[Function]} cbk [回调函数] * @retur ...
- 前端通过canvas实现图片压缩
在一次的项目中,需要用户上传图片,目前市场随便一个手机拍出来的照片都是好几兆,直接上传特别占用带宽,影响用户体验,所以要求对用户上传图片进行压缩后再上传:那么前端怎么实现这个功能呢? 亲测可将4M图片 ...
- 全网唯一的纯前端实现的canvas支持多图压缩并打包下载的工具
技术栈: canvas jszip.js(网页端压缩解压缩插件JSZIP库) FileSaver.js(文件保存到本地库) 直接解读源码: <div class="cont" ...
- HTML5 CANVAS 实现图片压缩和裁切
原文地址:http://leonshi.com/2015/10/31/html5-canvas-image-compress-crop/?utm_source=tuicool&utm_medi ...
- 利用html5 canvas实现纯前端上传图片的裁剪
今天跟大家分享一个前端裁剪图片的方法.许多网站都有设置用户头像的功能,用户可以选择一张本地的图片,然后用网站的裁剪工具进行裁剪,然后设置大小,位置合适的头像.当然,网上也有一些用js写的诸如此类裁剪的 ...
- 移动前端—H5实现图片先压缩再上传
在做移动端图片上传的时候,用户传的都是手机本地图片,而本地图片一般都相对比较大,拿iphone6来说,平时拍很多图片都是一两M的,如果直接这样上传,那图片就太大了,如果用户用的是移动流量,完全把图片上 ...
随机推荐
- JS Leetcode 374. 猜数字大小 题解分析
壹 ❀ 引 本题来自LeetCode 374. 猜数字大小,题目难度简单,与昨天的题目一样,也是一道标准二分法的题目,不知道是不是端午节的缘故,这两天的题目都比较简单,题目描述如下: 猜数字游戏的规则 ...
- NC50381 道路和航线
题目链接 题目 题目描述 FarmerJohn正在一个新的销售区域对他的牛奶销售方案进行调查.他想把牛奶送到T个城镇,编号为1到T.这些城镇之间通过R条道路(编号为1到R)和P条航线(编号为1到P)连 ...
- 解决Burpsuite1.6中文显示乱码问题
说明 最近公司项目被测试团队测试出有越权访问等安全问题,用的是这个Burpsuit工具,我想做软件测试的同学应该很熟悉.那么中间在模拟请求响应过程中发现返回的信息中文是乱码,搜索了一圈发现很多人提供的 ...
- U盘安装win7提示缺少所需的CD/DVD驱动器设备驱动程序
问题: 最近使用U盘启动盘安装win7,系统弹出提示框: 解决方法: U盘别插在usb3.0的口(蓝色),换成一个usb2.0的口就可以了
- 【Android逆向】frida 破解 jwxdxnx02.apk
apk 路径: https://pan.baidu.com/s/1cUInoi 密码:07p9 这题比较简单,主要是用于练习frida 1. 安装apk到手机 需要输入账号密码 2. 使用jdax 查 ...
- Oracle 插入数据报错 ORA-00918
1. 报错内容 ErrorCode = 918, SQLState = 23000, Details = ORA-00918: column 'TO_DATE('2023-12-1809:13:45' ...
- 【Azure 应用服务】Azure Function HTTP Trigger 遇见奇妙的500 Internal Server Error: Failed to forward request to http://169.254.130.x
问题描述 使用 Azure Funciton App,在本地运行完全成功的Python代码,发布到Azure Function就出现了500 Internal Server Error. 而且错误消 ...
- 【Azure 应用服务】通过 Web.config 开启 dotnet 应用的 stdoutLog 日志,查看App Service 产生500错误的原因
问题描述 Web App(App Service) 经常出现500错误,但是通过高级管理工具(Kudu站点)查看了所有的日志,均没有定位到具体的原因,有那些方式可以查看到更多的信息呢? 问题解答 HT ...
- Ubuntu上文件系统根目录磁盘空间扩充
今天使用Ubuntu的时候,出现了磁盘根目录空间不足的提示,需要我们对于根目录磁盘空间进行扩充. 1.打开终端输入命令,安装gparted管理器 sudo apt-get install gparte ...
- stm32f103ve+光电传感器使用教程+oled(HAL库)
最近想做一个物联网农业监控系统,第一步就是能够学会使用相关的外设,比如温湿度检测,光照强度检测,还有CO2检测等. 这次讲一下光电传感器的使用和代码实现. 1.知识储备:串口使用,ADC采集(此处用的 ...