前言
本文分享了 ImgTools(一个纯浏览器端图片处理工具)的完整实现过程。从技术选型到架构设计,从核心引擎到多模式 UI,涵盖了我在这过程中遇到的关键问题和解决方案。
为什么做这个工具
日常开发中经常需要压缩图片、转换格式、制作 Favicon,每次都要打开各种在线工具。这些工具往往需要上传文件到服务器,既慢又不安全。市面上已有的浏览器端方案要么功能单一,要么 WASM 引擎体积过大。我希望做一个本地运行、功能完整、且够快的工具。
技术选型
图像引擎:wasm-vips
| 方案 | 优点 | 缺点 |
| Canvas API | 无需额外依赖,简单场景开发快 | 格式支持有限,压缩质量不可控 |
| Squoosh 风格(多 WASM 编解码器) | 每个格式独立优化 | 5-8 个 WASM 包,集成复杂 |
| wasm-vips(libvips WASM 移植) | 单一库覆盖所有格式,API 统一 | WASM 约 5MB |
最终选择了 wasm-vips。它是 libvips 的 WASM 编译版本,一个库就能搞定 JPEG/PNG/WebP/AVIF/BMP/TIFF/GIF/SVG 的读写和压缩。API 统一,维护成本低。
前端框架
选择了 Vue 3 + Vite + TypeScript + Pinia。Vite 对 WASM 有原生支持,vue-tsc 提供类型检查,Pinia 的 Composition API 风格和 setup 语法配合得很好。
架构设计
分层结构
Components (UI) → Composables (逻辑) → Stores (状态) → Core (引擎)
- core/ — wasm-vips 封装、格式矩阵、处理流水线
- composables/ — 图片处理逻辑、批量导出
- stores/ — Pinia 全局状态
- components/ — UI 组件
核心流水线
File → ArrayBuffer → vips.Image.newFromBuffer → 解码
→ resize/crop 变换 → writeToBuffer('.jpg[Q=80]') → 编码
→ Blob → 预览 / 下载
编码方式使用 libvips 的内联格式字符串,如 .jpg[Q=80,optimize_coding=true],而不是各个格式独立的 save 方法。
关键问题与解决方案
1. SharedArrayBuffer 与跨域隔离
wasm-vips 使用 SharedArrayBuffer 实现多线程加速。浏览器要求页面设置两个 HTTP 头:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
配置在 vite.config.ts(开发环境)和 vercel.json(生产环境)。如果漏掉,wasm-vips 初始化时会报 DataCloneError。
2. WASM 初始化管理
wasm-vips 是 ~5MB 的 WASM 文件,初始化需要 1-3 秒。我做了几个设计:
- Promise 守卫 — 防止并发初始化
- 30 秒超时 — 加载失败时给出明确错误信息
- 事件监听机制 — 组件通过
onVipsReady订阅初始化完成事件
3. 自动降质
JPEG 二次编码时,如果质量设得比原图高,结果会反而更大。我实现了自动降质逻辑:
用户设 Q=80 → 编码 → 结果比原图大?
→ 自动降到 Q=70 → 还大?→ 继续降到 Q=60…
→ 直到文件真正变小或 Q≤10
这样用户永远不用担心”越压越大”的问题。
4. PNG 有损压缩
PNG 是天生无损的,但通过调色板量化(palette quantization)可以实现有损压缩:
// 有损 PNG:减少颜色数量
img.writeToBuffer('.png[palette=true,Q=80,dither=1.0,compression=9]')
// 无损 PNG:最高 zlib 压缩
img.writeToBuffer('.png[compression=9]')
5. Favicon 与 ICO 格式
wasm-vips 没有编译 ImageMagick 支持,所以无法直接生成 .ico 文件。解决方案是自己实现 ICO 打包:ICO 格式本质上是多张 PNG 的容器。我用 JS 实现了二进制打包。
6. PDF 转图片
wasm-vips 不直接支持 PDF。方案是 PDF.js + wasm-vips 组合:
PDF → PDF.js 逐页渲染到 Canvas
→ getImageData() 获取像素
→ wasm-vips newFromMemory() 编码为 PNG/JPEG/WebP
7. 动态 WASM 模块
部署到 Vercel 后,wasm-vips 的动态模块(vips-jxl.wasm、vips-heif.wasm)报了 404。原因是 Vite 构建时没有包含这些运行时加载的文件。修复方式:复制到 public/ 目录。
多模式架构
| 模式 | 功能 |
| 压缩 | 质量/有损无损/自动降质 |
| 转换 | 格式互转,最大质量编码 |
| Favicon | 裁剪 + 多尺寸 PNG/ICO/ZIP |
| 逐页/长图,精度可调 |
每个模式共享底层的处理引擎,但 UI 布局各自独立设计。
其他特性
- 暗黑模式 — CSS 变量 + 系统偏好检测 + 手动切换
- 多语言 — vue-i18n,自动检测浏览器语言,支持简中/繁中/英文
- PWA — vite-plugin-pwa,预缓存 ~15MB 资源,支持离线使用
- Vue Router(History 模式) — URL 直接对应模式,如
/compress、/pdf
总结
ImgTools 的核心思路是:选择单一成熟的 WASM 引擎(wasm-vips),在其基础上构建清晰的架构分层,针对每个模式做独立的 UI 优化。
技术栈选择上,Vue 3 + Vite + TypeScript 的组合对于 WASM 项目非常友好,Vite 对 WASM 的动态导入和资产处理都相当成熟。
踩过的坑主要集中在 wasm-vips 的浏览器兼容性(COOP/COEP)、动态 WASM 模块的构建问题、以及 PDF.js 的内存管理上。这些经验对于其他 WASM 项目也有参考价值。
项目地址:ImgTools
在线体验:https://img.rmb.ee
讨论
还没有留言,来留下第一条评论吧!