【React】使用browser-image-compression在上传前压缩图片、react上传图片压缩

文章目录


前言

下载依赖

bash 复制代码
npm install browser-image-compression -S

我这里是react 使用,本质上vue也是相同的用法,都是js,以及input调用

用的input标签进行上传,原汁原味,不使用组件,因为组件库界面不喜欢。

一、编写utils方法

utils/common.ts文件里面写公共方法

js 复制代码
import imageCompression from 'browser-image-compression';


/**
 * 压缩图片文件
 * @param file - 要压缩的图片文件
 * @param maxSizeMB - 最大文件大小(MB),超过此大小才压缩,默认 1MB
 * @returns Promise<File> - 返回压缩后的图片文件(如果小于 maxSizeMB 则返回原文件)
 */
export async function compressImage(file: File, maxSizeMB: number = 1): Promise<File> {
  // 检查文件大小(MB)
  const fileSizeMB = file.size / 1024 / 1024;

  // 如果文件小于等于 maxSizeMB,不需要压缩,直接返回原文件
  if (fileSizeMB <= maxSizeMB) {
    return file;
  }

  // 根据文件大小动态设置压缩选项
  // 注意:browser-image-compression 默认保持宽高比(等比例压缩)
  const options = {
    maxSizeMB: maxSizeMB, // 目标文件大小(MB)
    maxWidthOrHeight: 1920, // 最大宽度或高度(会等比例缩放,保持宽高比)
    useWebWorker: true, // 使用 Web Worker 进行压缩(不阻塞主线程)
    fileType: file.type, // 保持原文件类型
    // 压缩质量(0-1),如果不设置,库会根据 maxSizeMB 自动调整
    // initialQuality: 0.8, // 可选:初始压缩质量
  };

  try {
    const compressedFile = await imageCompression(file, options);
    console.log(
      `图片压缩完成: ${file.name}, 原始大小: ${fileSizeMB.toFixed(2)}MB, 压缩后: ${(compressedFile.size / 1024 / 1024).toFixed(2)}MB`
    );
    return compressedFile;
  } catch (error) {
    console.error('图片压缩失败:', error);
    // 压缩失败时返回原文件
    return file;
  }
}

二、使用步骤

1.标签使用

代码如下(示例):

js 复制代码
{/* 添加图片按钮 */}
<label className="block w-full border-2 border-dashed border-input rounded-lg cursor-pointer hover:border-primary transition-colors p-6 text-center">
  <input
    type="file"
    accept="image/png,image/jpeg,image/jpg"
    multiple
    className="hidden"
    onChange={async (e) => {
      const fileList = e.target.files;
      if (!fileList || fileList.length === 0) return;
      // 将 FileList 转换为数组
      const files = Array.from(fileList);
      // 验证所有文件类型
      const invalidFiles: File[] = [];
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        if (!file.type.match(/^image\/(png|jpeg|jpg)$/)) {
          invalidFiles.push(file);
        }
      }
      if (invalidFiles.length > 0) {
        alert('请上传 png、jpeg 或 jpg 格式的图片');
        e.target.value = '';
        return;
      }
      try {
        // 压缩图片(如果大于 1MB)
        const compressedFiles = await Promise.all(files.map((file) => compressImage(file, 1)));
        // 构建 FormData,使用动态下标
        const uploadFormData = new FormData();
        for (let i = 0; i < compressedFiles.length; i++) {
          uploadFormData.append(`images[${i}][file]`, compressedFiles[i]);
          uploadFormData.append(`images[${i}][subPath]`, 'product/goods');
        }
        // 上传图片
        CommonApi.uploadImages(uploadFormData)
          .then((res) => {
            if (Array.isArray(res.data)) {
              const newAssets = res.data
                .map((item: any, index: number) => {
                  if (item?.path) {
                    return {
                      scope: '',
                      file_name: files[index]?.name || '',
                      file_path: item.path,
                      sort_order: formData.assets.length + index,
                      alt_text: '',
                    };
                  }
                  return null;
                })
                .filter((asset): asset is NonNullable<typeof asset> => asset !== null);
              if (newAssets.length > 0) {
                // 执行赋值操作
              } else {
                console.error('上传图片接口返回的数据格式不正确:', res.data);
                alert('上传图片失败,请重试');
              }
            } else {
              console.error('上传图片接口返回的数据格式不正确:', res.data);
              alert('上传图片失败,请重试');
            }
          })
          .catch((error) => {
            console.error('上传图片失败: ', error);
            alert('上传图片失败,请重试');
          });
      } catch (error) {
        console.error('压缩图片失败: ', error);
        alert('处理图片失败,请重试');
      }
      // 清空input,允许重复上传同一文件
      e.target.value = '';
    }}
    ... 这里写你上传按钮或者图片图标
  />

2.分析

  1. label标签和input标签是联动的,把input隐藏, label内写其他的div,就可以做到点击触发上传
  2. 这是我的上传格式
  3. 返回值res.data是一个数组,
    如下
js 复制代码
{
    "ret": 1,
    "msg": "success",
    "data": [
        {
            "name": "20251202zO8PwOslh7U19qNW40Tf7sNjYOkRNHGLKeXxNacl.jpg",
            "path": "\/product\/goods\/20251202zO8PwOslh7U19qNW40Tf7sNjYOkRNHGLKeXxNacl.jpg",
            "width": 1200,
            "height": 1200
        }
    ]
}

使用compressImage别忘记了引入


总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

相关推荐
WHOVENLY1 小时前
【javaScript】- 笔试题合集(长期更新,建议收藏,目前已更新至31题)
开发语言·前端·javascript
指尖跳动的光1 小时前
将多次提交合并成一次提交
前端·javascript
程序员码歌1 小时前
短思考第263天,每天复盘10分钟,胜过盲目努力一整年
android·前端·后端
oden1 小时前
1 小时速通!手把手教你从零搭建 Astro 博客并上线
前端
若梦plus1 小时前
JS之类型化数组
前端·javascript
若梦plus1 小时前
Canvas 深入解析:从基础到实战
前端·javascript
若梦plus1 小时前
Canvas渲染原理与浏览器图形管线
前端·javascript
C_心欲无痕2 小时前
vue3 - 依赖注入(provide/inject)组件跨层级通信的优雅方案
前端·javascript·vue.js
幺零九零零2 小时前
全栈程序员-前端第二节- vite是什么?
前端