前段时间需要把一些图片转换成不同格式,特别是AVIF格式,当时先搜索一下在线的转换工具。
结果呢?找到的工具基本都是服务端转换,要么上传下载速度慢得让人怀疑人生,要么就是限制每天只能转几张图。最让人抓狂的是,有些工具还要注册登录,简直是在消费我们的耐心!
直到我突然意识到:既然AI写代码这么方便了,为什么不自己撸一个呢?于是就有了今天要分享的这次技术选型之旅,以及最终诞生的 avif-to-png.app 这个小工具。
技术方案横向对比:五大"选手"登场
在深入研究前端图片转换的过程中,我主要对比了五种技术方案。每种方案都有自己的特点和适用场景,让我们一一来看。
1. 浏览器原生 Canvas API
核心原理:Canvas.toBlob() 方法可以将画布内容转换为 Blob 对象,支持 PNG、JPEG、WebP 等格式。
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 绘制图片到canvas
ctx.drawImage(img, 0, 0);
// 转换为blob
canvas.toBlob((blob) => {
// 处理blob数据
const url = URL.createObjectURL(blob);
console.log('转换完成:', url);
}, 'image/png', 0.9);
优势分析:
- 兼容性极佳:自2020年1月起就得到了广泛的浏览器支持
- 零依赖:不需要引入任何第三方库
- 性能优异:直接调用浏览器底层API,速度很快
- 体积小:代码量极少,不会增加包体积
劣势分析:
- 格式限制:目前没有浏览器支持从Canvas直接创建AVIF格式
- 功能单一:只能做基础的格式转换,无法进行高级的图像处理
从社区维护角度来看,这是浏览器标准API,稳定性和可维护性都是顶级的。但对于AVIF这样的新格式,我们还得另想办法。
2. avif.js:优雅的Polyfill方案
核心原理:通过Service Worker拦截AVIF请求,将AVIF重新打包为单帧AV1视频进行解码。
// 注册Service Worker
require("avif.js").register("/avif-sw.js");
// 现在可以直接使用AVIF了
<img src="image.avif">
<div style="background: url(image2.avif)">content</div>
技术特点:
- 透明集成:拦截所有AVIF请求,适用于任何复杂的图像嵌入场景
- 体积控制:压缩后小于4KB,对项目影响很小
- 兼容性好:利用现有的AV1视频解码能力
局限性分析:
- 单向转换:主要用于AVIF解码显示,不支持编码
- 依赖限制:需要浏览器支持AV1视频解码
- 维护状态:社区维护相对较少,更新频率不高
这个方案适合纯展示场景,但对于我们需要的双向转换来说还不够。
3. sharp-wasm:Node.js巨头的浏览器进军
Sharp是Node.js生态中最知名的图像处理库,sharp-wasm是Sharp向WebAssembly和浏览器环境的移植版本。
import sharp from 'sharp-wasm';
// 初始化WASM模块
await sharp.init();
// 转换图片
const avifBuffer = await sharp(inputBuffer)
.avif({ quality: 80 })
.toBuffer();
技术优势:
- 功能全面:支持AVIF、SVG等多种格式,包含之前缺失的格式支持
- 性能强劲:基于libvips,处理速度很快
- API一致性:与Node.js版本保持相同的API设计
挑战与限制:
- 体积较大:完整的libvips移植,WASM文件体积不小
- 复杂度高:移植过程中遇到了同步编译、线程池等技术挑战
- 兼容性要求:对WebAssembly的支持要求较高
虽然功能强大,但对于轻量级的图片转换工具来说,有点"杀鸡用牛刀"的感觉。
4. wasm-vips:底层技术的直接暴露
wasm-vips是libvips的JavaScript/WebAssembly包装器,可以在浏览器中运行,是sharp-wasm的底层依赖。
import Vips from 'wasm-vips';
const vips = await Vips();
const image = vips.Image.newFromBuffer(inputBuffer);
const avifBuffer = image.avifsave_buffer({ Q: 80 });
技术分析:
- 底层控制:直接访问libvips的全部功能
- 灵活性高:可以进行复杂的图像处理操作
- 扩展性强:支持插件和自定义处理流程
使用门槛:
- 学习成本:需要了解libvips的API设计
- 文档限制:相比高层封装,文档和示例较少
- 稳定性:作为底层库,API变动的风险相对较高
这个方案更适合有深度图像处理需求的场景,对于简单的格式转换来说过于复杂。
5. Squoosh:Google的开源礼物
Squoosh是Google Chrome Labs开发的开源浏览器图像优化工具,利用WebAssembly运行各种图像编解码器。
import { decode } from '@jsquash/avif';
import { encode } from '@jsquash/png';
// 解码AVIF
const imageData = await decode(avifBuffer);
// 编码为PNG
const pngBuffer = await encode(imageData);
技术亮点:
- Google背书:Google Chrome Labs团队维护,技术实力有保障
- 模块化设计:每种格式独立打包,按需引入,支持Web Worker环境
- 性能优化:支持多线程处理,AVIF处理速度提升29%,OxiPNG提升61%
- 生产就绪:已在Squoosh.app上经过大量用户验证
生态状况:
- 社区活跃:@jsquash/avif在npm上有9个相关项目使用
- 持续更新:最近2个月前还有版本更新,维护状态良好
- 文档完善:提供了详细的使用示例和配置选项
让我印象深刻的是,Next.js甚至移除了内置的Squoosh支持,转而使用Sharp作为可选依赖,这说明在服务端场景下Sharp确实更有优势,但在浏览器端Squoosh仍然是很好的选择。
最终选型:原生+Squoosh的组合拳
经过深入的技术调研和测试,我最终选择了浏览器原生Canvas API + Squoosh的组合方案。
选型逻辑:
- 按需选择:对于浏览器原生支持的格式(PNG、JPEG、WebP),直接使用Canvas API,速度快、体积小
- 能力补强:对于AVIF这样的新格式,使用@jsquash/avif进行处理
- 最佳平衡:在功能完整性、性能表现、维护成本之间找到了最佳平衡点
实现示例:
async function convertImage(file, targetFormat) {
const img = await loadImage(file);
const canvas = createCanvas(img);
if (['png', 'jpeg', 'webp'].includes(targetFormat)) {
// 使用原生API
return new Promise(resolve => {
canvas.toBlob(resolve, `image/${targetFormat}`, 0.9);
});
} else if (targetFormat === 'avif') {
// 使用Squoosh
const imageData = getImageData(canvas);
const { encode } = await import('@jsquash/avif');
return await encode(imageData);
}
}
这个方案在我的 avif-to-png.app 上运行得非常好,转换速度快,体验流畅,而且完全在浏览器端处理,不用担心隐私问题。