【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提供了大量能使我们快速便捷地处理数据的函数和方法。

相关推荐
前端_yu小白43 分钟前
前端实现录音,获取流分析音量大小,设置相应的动画
前端·mediarecorder·录音·浏览器安全性检查·https部署
虎子_layor44 分钟前
小程序登录到底是怎么工作的?一次请求背后的三方信任链
前端·后端
草字1 小时前
css 父节点设置display: flex; align-items: center;,子节点如何跟随其他子节点撑高的高度
前端·javascript·css
我命由我123451 小时前
微信小程序 - 页面跳转并传递参数(使用路由参数、使用全局变量、使用本地存储、使用路由参数结合本地存储)
开发语言·前端·javascript·微信小程序·小程序·前端框架·js
DJ斯特拉1 小时前
日志技术Logback
java·前端·logback
HIT_Weston1 小时前
49、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 单/多线程分析(一)
前端·ubuntu·gitlab
涔溪1 小时前
Vue3 中ref和reactive的核心区别是什么?
前端·vue.js·typescript
Gomiko1 小时前
JavaScript基础(九):内部对象
开发语言·javascript·udp
天意__1 小时前
Flutter开发,scroll_to_index适配flutter_list_view
前端·flutter