优化实战 第 62 期 - 前端压缩上传图片的必要性

1,941 阅读2分钟

由于业务需要,用户要通过移动端上传图片,并且前端要对上传图片进行压缩,然后再上传到服务器,这样可以 减少移动端上行流量,减少用户上传等待时长,优化用户体验

compress.png

获取文件对象

  • 普通函数

    fileInputEl.addEventListener('change', function() {
      const files = [...this.files]
    })
    
  • 箭头函数

    fileInputEl.addEventListener('change', ({target}) => {
      const files = [...target.files]
    })
    
  • 踩坑问题

    选择文件后,文件名会存放在表单的 value 值中,如果该值选取前后不发生变化,就不会触发 change 事件

    打开文件选择对话框,在未选取任何文件对象的情况下,点击取消关闭文件选择框时,会触发 change 事件

  • 解决方案

    在获取到文件对象后,清空表单的 value

    fileInputEl.addEventListener('change', ({target}) => {
      const files = [...target.files]
      target.value = null
    })
    

压缩文件对象

  • 通过 file 对象获取 Image 对象
    const getImage = file => {
      return new Promise((resolve, reject) => {
        const image = document.createElement('img')
        const temporaryUrl = URL.createObjectURL(file)
        Object.assign(image, {
          onload() {
            resolve(image)
            URL.revokeObjectURL(temporaryUrl)
          },
          onerror: reject,
          src: temporaryUrl
        })
      })
    }
    
  • 将 Image 对象绘画在 Canvas 对象上
    const paintingImageToCanvas = image => {
      const {naturalWidth, naturalHeight} = image
      const canvas = document.createElement('canvas')
      Object.assign(canvas, {
        width: naturalWidth, height: naturalHeight
      })
      canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height)
      return canvas
    }
    
  • 通过 Canvas 对象压缩文件对象
    const compressPicture = ([file], quality = 0.65) => {
      return new Promise(async resolve => {
        const image = await getImage(file)
        const canvas = paintingImageToCanvas(image)
        canvas.toBlob(blob => resolve(blob), 'image/jpeg', quality)
      })
    }
    
    通过调整 quality 参数来指定压缩图片的展示质量,取值在 01 之间

等比例缩放压缩

  • 缩放原理

    通过等比例缩放可以有效避免压缩图片变形

    输出目标高度 * 宽高比(aspectRatio)= 等比缩放后的宽

    若比输出目标宽度小,则以输出目标高度为基准缩放,否则以宽度为基准缩放

  • 缩放计算

    const uniformScale = ({naturalWidth, naturalHeight}, width, height) => {
      const aspectRatio = naturalWidth / naturalHeight // Image 对象原始尺寸宽高比
      width = width || naturalWidth
      height = height || naturalHeight
      if (height * aspectRatio > width) {
        height = width / aspectRatio
      } else {
        width = height * aspectRatio
      }
      return { width, height }
    }
    
  • 应用压缩

    在 getImage 方法中给 Image 对象设置等比宽高

    Object.assign(image, {
      onload() {
        const targetSize = uniformScale(this, width, height)
        Object.assign(this, targetSize)
        resolve(image)
        URL.revokeObjectURL(temporaryUrl)
      }
    })
    

    在 paintingImageToCanvas 方法中读取 Image 对象的宽高设置给 Canvas 对象

    const paintingImageToCanvas = (image) => {
      const canvas = document.createElement('canvas')
      Object.assign(canvas, {
        width: image.width, height: image.height
      })
      canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height)
      return canvas
    }
    

    一起学习,加群交流看 沸点