一、背景

前端对图片进行 剪裁 或者 大小进行压缩 都是比较场景的需求。但是这次接到一个 将图片尺寸缩小 的需求,为啥呢?因为在前端上传到服务器后,有个功能会导出 word,而图片就会放到里面去导出,而图片尺寸太大,显示会有问题;例如简历个人信息里面的身份证、学历、头像等照片(这种问题其实我觉得应该由后端处理,但既然知道了,就研究研究)。

二、实现思路

有个问题需要先确定:缩放实现在上传前还是上传后,区别是什么?
el-upload组件中一般都有大图预览功能,即el-image 中的预览功能previewSrcList

  • 上传前缩小:保存后的图片较小,预览大图也不能看到原始尺寸(没保存前可以看到)
  • 上传后缩小:需要上传两张图,并且小图需要做无感上传,原始图和缩小后的图片,并且需要定义区分规则,怎么获取大图,怎么获取小图

由于项目中对大图展示要求不高,故采用 上传前缩小 实现;

三、代码实现

el-upload 二次封装代码不尽相同,故在此只展现压缩相关核心代码

compressImgSize(file) { // 压缩图片
  // file 里面没有url 字段(el-upload自带,用于图片展示的临时路径),则通过URL.createObjectURL(file)转化为临时路径
  // const fileUrl = URL.createObjectURL(file)
  // const image = new Image();
  // image.src = fileUrl;

  const image = new Image();
  image.src = file.url;
  image.onload = (e) => { // onload 才能获取到图片真实的宽高
    console.log('image.width', image, image.width, image.height)

    if (image.width > 0 && image.height > 0) {
      const oldScale = Number((image.width / image.height).toFixed(2));

      let maxWidth = 200, maxHeight = 200; // 定义缩放后的最大宽高
      let targetWidth = image.width, targetHeight = image.height; // 图片缩放后的真实宽高, 默认为图片的原始尺寸
      if (targetWidth > maxWidth) { // 目标宽度大于最大宽度
        targetWidth = maxWidth;
        targetHeight = targetWidth / oldScale;
      }
      //缩放后高度仍然大于最大高度继续按比例缩小
      if (targetHeight > maxHeight) {
        targetHeight = maxHeight
        targetWidth = targetHeight * oldScale;
      }

      const canvas = document.createElement('canvas'); // 创建一个canvas节点
      const context = canvas.getContext('2d');
      canvas.width = targetWidth; // 设置canvas的宽高
      canvas.height = targetHeight;
      context.drawImage(image, 0, 0, targetWidth, targetHeight); // 将图片绘制到canvas内部
      const imageBase64 = canvas.toDataURL('image', 0.92);//canvas导出成为base64
      this.upData.file = this.base64ToFile(imageBase64, 'file') // 将base64图片转为 file 对象

      this.$refs.upload.submit(); // 触发上传
    }
  }
  

},

// base64图片转file的方法(base64图片, 设置生成file的文件名)
base64ToFile(base64, fileName) {
  // 将base64按照 , 进行分割 将前缀  与后续内容分隔开
  let data = base64.split(',');
  // 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
  let type = data[0].match(/:(.*?);/)[1];
  // 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
  let suffix = type.split('/')[1];
  // 使用atob()对base64数据进行解码  结果是一个文件数据流 以字符串的格式输出
  const bstr = window.atob(data[1]);
  // 获取解码结果字符串的长度
  let n = bstr.length
  // 根据解码结果字符串的长度创建一个等长的整形数字数组
  // 但在创建时 所有元素初始值都为 0
  const u8arr = new Uint8Array(n)
  // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
  while (n--) {
    // charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
    u8arr[n] = bstr.charCodeAt(n)
  }
  // 利用构造函数创建File文件对象
  // new File(bits, name, options)
  const file = new File([u8arr], `${fileName}.${suffix}`, {
    type: type
  })
  // 将File文件对象返回给方法的调用者
  return file;
}

注意点:

  1. compressImgSize 压缩方法调用位置在 上传文件之前的钩子 before-upload 或者是 文件状态改变时的钩子 on-change
  2. 上传文件之前的钩子 before-upload 拿到的 file 没有url,需要通过 URL.createObjectURL(file) 转化
  3. 文件状态改变时的钩子 on-change 里面有临时路径 url,可直接使用
  4. this.$refs.upload.submit() 手动触发的上传方法,文件默认还是原始图片,需要通过配置 data: { file: this.upData.fil } => 上传时附带的额外参数 实现上传缩小后的图片;但el-upload默认在上传时携带了file参数并不可删除(但是可以用过配置name: "oldFile"去修改,保证后端不再使用到,查阅el-upload源码可知,文档没有),所以可以通过配置 name="file2" 将原本的file => 修改成file2 即可。

ps:
最佳方式还是由后端重新写一个 接口A,在导出接口中调用 接口A 时,将图片临时处理缩小后返回即可,这种操作不能影响到前端大图展示
 

文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!

Logo

前往低代码交流专区

更多推荐