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 对图片进行压缩,主要通过调整图片尺寸和质量来实现。

javascript 复制代码
/**
 * 图片压缩工具
 * @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;

      // 绘制图片到Canvas
      const ctx = canvas.getContext('2d');
      if (!ctx) {
        resolve(null);
        return;
      }
      
      ctx.drawImage(img, 0, 0, width, height);

      // 将Canvas内容转换为Blob
      canvas.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 组件中使用示例
html 复制代码
<template>
  <div class="image-upload">
    <input
      type="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 示例)

javascript 复制代码
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 });
});

四、注意事项

  1. 压缩会损失一定画质,需根据业务场景平衡画质与文件大小;
  2. 对于几十 MB 的超大图片,可考虑分阶段压缩(先大幅缩小尺寸,再调整质量);
  3. 服务端必须进行二次校验,防止前端绕过压缩逻辑上传超大文件。
相关推荐
慧一居士3 小时前
vue 中 directive 作用,使用场景和使用示例
前端
慧一居士3 小时前
vue 中 file-saver 功能介绍,使用场景,使用示例
前端
ByteCraze3 小时前
秋招被问到的常见问题
开发语言·javascript·原型模式
优弧3 小时前
Vue 和 React 框架对比分析:优缺点与使用场景
vue.js
渣哥4 小时前
从代理到切面:Spring AOP 的本质与应用场景解析
javascript·后端·面试
文心快码BaiduComate4 小时前
文心快码3.5S实测插件开发,Architect模式令人惊艳
前端·后端·架构
UIUV4 小时前
JavaScript代理模式实战解析:从对象字面量到情感传递的优雅设计
javascript
Kimser4 小时前
基于 VxeTable 的高级表格选择组件
前端·vue.js