当前位置: 首页 > news >正文

JavaScript 系列之:图片压缩

图像压缩原理

什么是有损压缩?

不会完全真实的记录图片信息,会根据人眼观察世界的特性,忽略掉部分会被人眼忽略的颜色信息,代之以邻近的颜色。解压后无法完全恢复原始图像,会损失一定质量。压缩率可通过参数调整,压缩率越高,图像质量损失越明显。

什么是无损压缩?

无损压缩会完整记录图片颜色信息,但是相同颜色的区域,会被压缩记录,因此无损压缩也可以比较完整的还原图片。压缩率通常较低。

格式压缩方式透明度支持动画支持兼容性摘要
JPEG有损不支持不支持所有浏览器都兼容静态图像有损压缩的理想选择(目前最流行)
PNG无损支持不支持除了 IE6 以外的所有浏览器都兼容与 JPEG 相比,PNG 能更精确地再现源图像,或在需要透明的情况下更受青睐
GIF无损支持支持所有浏览器都兼容是简单图像和动画的不错选择
WebP有损和无损支持支持兼容性较差,只有主流浏览器的较新版本才支持是静止图像和动画图像的【绝佳选择】,WebP 的压缩效果比 PNG 或 JPEG 好得多
HIEF有损和无损支持支持兼容性较差,主要在 Apple 生态系统下一代高效图像格式,压缩效率显著优于 JPEG/PNG,节省大量存储空间。

前端压缩方法

使用 canvas

主流方法:

利用 Canvas 的绘图能力,使用 drawImage 以及 toDataURL 这两个 API,通过调整图片的尺寸或者绘图质量,来达到图片压缩的效果。

  • 优点:实现简单,参数可配置化,可自定义图片尺寸,指定区域裁剪等等。

  • 缺点:只有 jpeg 、webp 支持原图尺寸下图片质量的调整,来达到压缩图片的效果,其他图片格式仅能通过调整尺寸来实现。

实现过程:

  1. 使用 FileReader 和 Image 对象加载图片

  2. 将图片绘制到 <canvas> 上,通过调整 <canvas> 的尺寸或质量来控制压缩效果

  3. 使用 canvas.toDataURL()canvas.toBlob() 方法导出新图片

核心代码

const compressImage = (file, options = {}, callback) => {const {maxSize = 100,    // 默认最大100kbmaxWidth,         // 无默认值,不传递则不限制maxHeight,        // 无默认值,不传递则不限制quality = 0.9     // 默认质量0.9} = options;/*** 首先,我们需要创建一个Canvas元素,并将图像绘制到Canvas上。* 然后,我们可以调整Canvas的大小,以实现图像的压缩。*/const reader = new FileReader();reader.onload = (e) => {const img = new Image();img.onload = () => {const canvas = document.createElement('canvas');let width = img.width;let height = img.height;if (maxWidth && width > height) {if (width > maxWidth) {height *= maxWidth / width;width = maxWidth;}} else {if (maxHeight && height > maxHeight) {width *= maxHeight / height;height = maxHeight;}}canvas.width = width;canvas.height = height;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0, width, height);/*** HTMLCanvasElement.toBlob() 方法创造 Blob 对象,用以展示 canvas 上的图片;* toBlob(callback, type, quality)* callback*    回调函数,可获得一个单独的 Blob 对象参数。如果图像未被成功创建,可能会获得 null 值。* type 可选*    DOMString 类型,指定图片格式,默认格式(未指定或不支持)为 image/png。*    type 最好手动指定为 image/jpeg 格式,因为 png 不支持有损压缩,即不能通过设置质量来进行压缩,webp 兼容性差,所以 jpeg 是最合适的。* quality 可选*    Number 类型,值在 0 与 1 之间,当请求图片格式为 image/jpeg 或者 image/webp 时用来指定图片展示质量。如果这个参数的值不在指定类型与范围之内,则使用默认值,其余参数将被忽略。* 返回值*    无*/canvas.toBlob(function (blob) {if (!blob) {console.error('压缩失败');callback(null);return;}if (blob.size / 1024 <= maxSize || quality <= 0.1) {callback(blob, file);} else {const newFile = new File([blob], file.name, {type: blob.type,lastModified: Date.now()});compressImage(newFile, { ...options, quality: quality - 0.1 }, callback);}}, 'image/jpeg', quality);},img.onerror = (e) => {console.log("图片加载失败:", e);}img.src = e.target.result;}reader.onerror = (e) => {console.log("文件读取失败:", e);}/*** readAsDataURL:将文件内容读取为 Base64 编码的 Data URL。* 适用场景:*    图片/文件的预览(如 <img src="data:image/png;base64,...">)*    需要将文件直接嵌入网页或上传到服务器(Base64 格式)。* * readAsText:将文件内容读取为 纯文本字符串。* 适用场景:*    读取 .txt、.csv、.json 等文本文件。* * readAsArrayBuffer:将文件读取为 ArrayBuffer(二进制数据缓冲区)。* 适用场景:*    处理二进制文件(如图片、音频、视频的原始字节)。*/reader.readAsDataURL(file);
}

初学者可能看的比较迷糊,这里来详细解释一下:

这种写法是 JavaScript 处理异步操作的典型模式:先定义好所有的处理规则和回调函数,最后才启动实际的异步操作(文件读取),确保操作完成时所有的处理函数都已准备就绪。简单来说就是“先准备后执行”。

代码解释:

// 创建一个文件读取器,用于读取文件内容。
const reader = new FileReader();
// 文件成功读取时触发
reader.onload = (e) => {// ... 函数体 ...
}
// 图片成功加载完成时触发
img.onload = () => {// ... 压缩处理的核心逻辑 ...
}
// 设置图片源,触发图片加载
img.src = e.target.result;
// 开始读取文件
reader.readAsDataURL(file);

所以上面这段代码的逻辑是:

  1. 首先定义了 reader.onload(文件读取成功的回调函数)

  2. 在 reader.onload 内部定义了 img.onload(图片加载成功的回调函数)

  3. 通过 reader.readAsDataURL(file) 开始读取文件

  4. 文件读取成功后执行 reader.onload

  5. 在 reader.onload 中通过 img.src = e.target.result 开始加载图片

  6. 图片加载成功后执行 img.onload

兼容性问题

这段代码在 PC 和 安卓手机上都能完美运行,能够极大压缩图片质量,但是在 IOS 设备上就差强人意,压缩效果不及安卓,甚至会出现越大越大的情况。

经查阅资料可能是以下两个原因造成的:

  • 在 IOS 设备中会有 HIEF 格式的图片,而 HEIF 是一种比 JPEG 高效得多的压缩格式(相同画质下文件更小),所以当把 HEIF 转为 JPEG 时,就可能因为格式本身的压缩效率差异导致文件变大

  • 安卓设备浏览器通常使用 Blink 内核,而 IOS 设备浏览器使用 WebKit 内核。(微信内置浏览器安卓设备是 X5 内核,基于 Blink 内核开发,与 Chrome 同源,IOS 设备也是 WebKit 内核)

    • Blink 可能会更激进地丢弃图像细节以减小体积,而 WebKit 可能保留更多细节

    • Blink 在低 quality 下可能压缩得更彻底,而 WebKit 对低 quality 值(如 0.1 以下)的处理更保守,即使设置极低的 quality,也不会无限制压缩(避免图像过度失真)。

针对 IOS 设备的解决方法:

  • 缩小图片尺寸

  • 动态调整 quality 步长,把每次减 0.1 改成每次减 0.3 或者更多

  • 降低初始 quality 初始值,例如直接设置 quality 为 0.1。

我使用第三种方法直接将 quality 初始值设置为 0.1,经过实际测试,确实能有效降低压缩后图片大小。

完整代码示例:

<template><div><input type="file" id="file1" @change="fileChange" /><input type="file" id="file2" @change="(e) => fileChange(e, 0.5)" /><div>{{ loading }}</div></div>
</template><script setup>
import { onMounted, ref } from 'vue';
const loading = ref('')
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;onMounted(() => { })const fileChange = (e, quality) => {const file = e.target.files[0];const maxSize = 100;console.log("是IOS设备吗:", isIOS);if (file.size / 1024 <= maxSize) {alert(`图片大小${file.size / 1024}KB小于${maxSize}KB,无需压缩,直接下载`);const blob = new Blob([file], { type: file.type });createDownloadLink(blob, file);return;}alert(`图片原始大小:${(file.size / 1024).toFixed(2)}KB,${(file.size / 1024 / 1024).toFixed(2)}MB,类型${file.type}`)loading.value = '图片压缩中。。。'compressImage(file, { maxSize, quality: isIOS ? 0.1 : quality }, compressCallback);
}// 这里的file是为了获取原来的文件名用的
const compressCallback = (blob, file) => {alert(`图片压缩完毕后大小:${(blob.size / 1024).toFixed(2)}KB,${(blob.size / 1024 / 1024).toFixed(2)}MB`)createDownloadLink(blob, file)loading.value = '图片压缩完毕!'
}const compressImage = (file, options = {}, callback) => {const {maxSize = 100,    // 默认最大100kbmaxWidth,         // 无默认值,不传递则不限制maxHeight,        // 无默认值,不传递则不限制quality = 0.9     // 默认质量0.9} = options;console.log("compressImage quality:", quality);const reader = new FileReader();reader.onload = (e) => {const img = new Image();img.onload = () => {const canvas = document.createElement('canvas');let width = img.width;let height = img.height;if (maxWidth && width > height) {if (width > maxWidth) {height *= maxWidth / width;width = maxWidth;}} else {if (maxHeight && height > maxHeight) {width *= maxHeight / height;height = maxHeight;}}canvas.width = width;canvas.height = height;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0, width, height);canvas.toBlob(function (blob) {if (!blob) {console.error('压缩失败');callback(null);return;}if (blob.size / 1024 <= maxSize || quality <= 0.1) {callback(blob, file);} else {const newFile = new File([blob], file.name, {type: blob.type,lastModified: Date.now()});compressImage(newFile, { ...options, quality: quality - 0.1 }, callback);}}, 'image/jpeg', quality);},img.onerror = (e) => {console.log("图片加载失败:", e);}img.src = e.target.result;}reader.onerror = (e) => {console.log("文件读取失败:", e);}reader.readAsDataURL(file);
}/*** 创建下载链接* @param blob * @param file */
const createDownloadLink = (blob, file) => {let url = window.URL.createObjectURL(blob);console.log("url:", url, typeof url);let a = document.createElement("a");a.href = url;a.download = file.name; // blob对象本身并不直接存储文件名document.body.appendChild(a)a.click();document.body.removeChild(a)window.URL.revokeObjectURL(url)
}</script><style scoped></style>

使用算法

简单来说,通过算法减少图片上的颜色差异,牺牲图片画质。比如紧挨着的颜色相近的四个像素的颜色信息压缩前大概占16个字节,压缩后变成一个颜色就能减少近4倍。自然被压缩后文件就变小,画质自然也会降低。

  • 优点:色彩丰富场景压缩率更高,参数可配置化,可自定义图片的尺寸,图片的质量等。

  • 缺点:图片质量压缩损失更大。

参考内容

前端性能优化系列 - 图像压缩篇

http://www.dtcms.com/a/342479.html

相关文章:

  • 微信小程序设计的请求封装方案(request.js)
  • NPM模块化总结
  • DINOv3 重磅发布
  • 计算机网络技术学习-day6《三层交换机配置》
  • python发布文章和同步文章到社区的工具小脚本
  • 第三阶段数据库-6:sql中函数,多表查询,运算符,索引,约束
  • 智慧城管云平台源码,微服务vue+element+springboot+uniapp技术架构,数字化综合执法办案系统
  • 数据结构之排序大全(4)
  • 苷类成分通过 PI3K/AKT 信号通路促进内皮祖细胞来源外泌体修复受损血管内皮
  • 基于YOLO11的茶叶病害智能检测系统
  • 组态软件——工业监控“大脑”
  • leetcode-python-242有效的字母异位词
  • 代码随线录刷题Day39
  • 【uni-app】自定义导航栏以及状态栏,胶囊按钮位置信息的获取
  • Java的运行时数据区
  • Notepad++换行符替换
  • 机器学习——AdaBoost算法
  • 基于YOLO11的水稻叶片病害检测项目
  • 面试压力测试破解:如何从容应对棘手问题与挑战
  • (第二十期上)HTML 超链接标签 a
  • 【工具】前端JS/VUE修改图片分辨率
  • C语言数据结构:动态顺序表实现与应用
  • 如何使用Prometheus + Grafana + Loki构建一个现代化的云原生监控系统
  • 数字社会学是干什么的?数字社会学理论与数字社会学家唐兴通讲数字社会学书籍有哪些?AI社会学人工智能社会学理论框架
  • 4090服务器无法sudo apt update 问题解决
  • 告别服务器!Amazon Lambda无服务开发实战指南
  • CI/CD 学习之路
  • 佰钧成 社招 一面
  • Cesium 实战 27 - 自定义纹理材质 - 立体墙(渐变色)
  • 【数据结构入门】排序算法:插入排序