Vue图片压缩方案
在 Vue 项目中处理用户上传的大图片时,通过前端压缩减小图片体积是优化上传体验的有效方式。以下为具体实现方案及相关说明:
一、图片压缩方案对比
不同压缩方案在体积缩减、兼容性、耗时等方面各有特点,具体如下:
方案 | 体积缩减 | 兼容性 | 耗时 | 备注 |
---|---|---|---|---|
Canvas 缩放 | 60-85% | 99% | 中 | 本文主方案 |
WebP encode | 额外-30% | 92% | 高 | 需服务端配合回退 JPG |
WebAssembly | 70-90% | 92% | 高 | mozJPEG/avif-js,包大 |
服务端压缩 | 0% | 100% | 低 | 前端仍要先传原图 |
二、核心实现:Canvas 缩放压缩
利用 HTML5 的 Canvas API,通过调整图片尺寸和质量实现压缩,是兼容性和效果平衡较好的方案。
1. 压缩工具函数
核心思路是利用 HTML5 的 Canvas API 对图片进行压缩,主要通过调整图片尺寸和质量来实现。
/*** 图片压缩工具* @param file 原始图片文件* @param options 压缩选项* @returns 压缩后的图片文件*/
export const compressImage = async (file: File,options: {maxWidth?: number; // 最大宽度maxHeight?: number; // 最大高度quality?: number; // 图片质量 0-1} = {}
): Promise<File | null> => {// 默认配置const {maxWidth = 1200,maxHeight = 1200,quality = 0.8} = options;return new Promise((resolve) => {// 创建图片对象const img = new Image();const objectUrl = URL.createObjectURL(file);img.src = objectUrl;img.onload = () => {URL.revokeObjectURL(objectUrl);// 计算压缩后的尺寸(保持比例)let width = img.width;let height = img.height;// 如果图片尺寸超过最大限制,按比例缩小if (width > maxWidth || height > maxHeight) {const ratio = Math.min(maxWidth / width, maxHeight / height);width = width * ratio;height = height * ratio;}// 创建Canvas元素const canvas = document.createElement('canvas');canvas.width = width;canvas.height = height;// 绘制图片到Canvasconst ctx = canvas.getContext('2d');if (!ctx) {resolve(null);return;}ctx.drawImage(img, 0, 0, width, height);// 将Canvas内容转换为Blobcanvas.toBlob((blob) => {if (!blob) {resolve(null);return;}// 转换为File对象const compressedFile = new File([blob],file.name,{ type: blob.type, lastModified: Date.now() });resolve(compressedFile);},file.type || 'image/jpeg',quality);};img.onerror = () => {URL.revokeObjectURL(objectUrl);resolve(null);};});
};
2. Vue3 组件中使用示例
<template><div class="image-upload"><inputtype="file"accept="image/*"@change="handleFileChange"class="file-input"/><div v-if="previewUrl" class="preview"><img :src="previewUrl" alt="预览图" class="preview-img" /><p>原始大小: {{ originalSize }}KB</p><p v-if="compressedSize">压缩后大小: {{ compressedSize }}KB</p></div></div>
</template><script setup lang="ts">
import { ref } from 'vue';
import { compressImage } from '@/utils/imageCompressor';const previewUrl = ref('');
const originalSize = ref(0);
const compressedSize = ref(0);const handleFileChange = async (e: Event) => {const input = e.target as HTMLInputElement;if (!input.files || input.files.length === 0) return;const file = input.files[0];// 显示原始文件大小originalSize.value = Math.round(file.size / 1024);// 如果文件小于500KB,不压缩直接使用if (file.size < 500 * 1024) {previewUrl.value = URL.createObjectURL(file);compressedSize.value = originalSize.value;return;}// 压缩图片const compressedFile = await compressImage(file, {maxWidth: 1000,maxHeight: 1000,quality: 0.7});if (compressedFile) {previewUrl.value = URL.createObjectURL(compressedFile);compressedSize.value = Math.round(compressedFile.size / 1024);// 这里可以将compressedFile上传到服务器// await uploadToServer(compressedFile);}
};
</script><style scoped>
.image-upload {display: flex;flex-direction: column;gap: 1rem;
}.file-input {padding: 0.5rem;border: 1px solid #ccc;border-radius: 4px;
}.preview {margin-top: 1rem;
}.preview-img {max-width: 100%;border-radius: 4px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
三、服务端二次校验(Node 示例)
router.post('/upload', multer().single('file'), async (req, res) => {const { buffer, mimetype, size } = req.file;if (size > 1024 * 1024) return res.status(413).send('too big');if (!['image/jpeg', 'image/webp'].includes(mimetype)) return res.status(400).send('bad type');// 计算 SSIM 或 Magic Number 防止“伪造压缩”const metadata = await sharp(buffer).metadata();if (metadata.width! > 1600) return res.status(400).send('width overflow');// 真正落盘await putToOSS(buffer, mimetype);res.json({ url });
});
四、注意事项
- 压缩会损失一定画质,需根据业务场景平衡画质与文件大小;
- 对于几十 MB 的超大图片,可考虑分阶段压缩(先大幅缩小尺寸,再调整质量);
- 服务端必须进行二次校验,防止前端绕过压缩逻辑上传超大文件。