【vue】 typeScript OSS图片压缩处理工具类

import Exif from 'exif-js'
import logUtil from '@/utils/logUtil'

/**
 * 图片压缩处理工具
 * 
 * @hxq/utils打包为ES5后在安卓4.4.4版本中会报错。!!!!
 * 先拷贝到此。2019.12.28
 * 
 * `ImageUtil.handle(file, 0.1)`
 */
export class ImageUtil {
  /**
   * 处理图片
   * @param file 文件
   * @param quality 质量 0~1
   */
  public static handle (file: File, quality = 0.8): Promise<Blob> {
    const fileReader = FileReader

    return new Promise(async (resolve, reject) => {
      // 看支持不支持FileReader
      if (!file || !fileReader) {
        reject(new Error('不支持压缩'))
      }

      if (/^image/.test(file.type)) {
        try {
          const orientation = await ImageUtil.getOrientation(file)
          // 创建一个reader
          const reader = new FileReader()
          // 将图片转成 base64 格式
          reader.readAsDataURL(file)
          // 读取成功后的回调
          reader.onloadend = () => {
            const result: any = reader.result
            const img = new Image()
            img.src = result
            // 判断图片是否大于100K,是就压缩,反之直接上传
            if (result.length <= (100 * 1024)) {
              resolve(file)
            } else {
              img.onload = () => {
                const baseData = ImageUtil.compress(img, orientation, quality)
                resolve(ImageUtil.b64toBlob(baseData))
              }
            }
          }
        } catch (error) {
          reject(error)
          logUtil.report({
            model: 'code',
            method: 'imageUtil-handler',
            code: '图片压缩',
            msg: error.stack // 上报栈信息 2020.04.08
          })
        }
      } else {
        reject(new Error('请上传图片文件: ' + file.type))
      }
    })
  }

  /**
   * base64转Blob
   * @param b64Data base64字符串
   * @param sliceSize 分片大小
   * @returns Blob 阿里上传图片需要Blob型
   */
  public static b64toBlob (baseData: string, sliceSize = 512) {
    const contentType = ImageUtil.base64ContentType(baseData)
    const realData = ImageUtil.base64RealData(baseData)
    const byteCharacters = atob(realData)
    const byteArrays = []
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize)
      const byteNumbers = new Array(slice.length)
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)
      byteArrays.push(byteArray)
    }
    return new Blob(byteArrays, {
      type: contentType
    })
  }

  /**
   * dataUrl图片类型
   * @param b64Data base64字符串
   */
  public static base64ContentType (b64Data: string) {
    const block = b64Data.split(';')
    const contentType = block[0].split(':')[1]
    return contentType
  }

  /**
   * dataUrl中的base64数据
   * @param b64Data base64字符串
   */
  public static base64RealData (b64Data: string) {
    const block = b64Data.split(';')
    const realData = block[1].split(',')[1]
    return realData
  }

  /**
   * 去获取拍照时的信息,解决拍出来的照片旋转问题
   * @param file 文件对象
   */
  public static getOrientation (file: File): Promise<number> {
    return new Promise((resolve, reject) => {
      Exif.getData(file as any, () => {
        const orientation = Exif.getTag(file, 'Orientation')
        resolve(orientation)
      })
    })
  }

  /**
   * 旋转图片
   * @param img 图片
   * @param degreeNum 旋转角度,如:90、-90、180
   * @param canvas 画布
   */
  public static rotateImg (img: HTMLImageElement, degreeNum: number, canvas: HTMLCanvasElement) {
    const width = canvas.width
    const height = canvas.height
    // 旋转角度以弧度值为参数
    const degree = degreeNum * Math.PI / 180
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
    switch (degreeNum) {
      case 0:
        break
      case 90:
        canvas.width = height
        canvas.height = width
        ctx.rotate(degree)
        ctx.drawImage(img, 0, -height, width, height)
        break
      case 180:
      case -180:
        ctx.rotate(degree)
        ctx.drawImage(img, -width, -height, width, height)
        break
      case 270:
      case -90:
        canvas.width = height
        canvas.height = width
        ctx.rotate(degree)
        ctx.drawImage(img, -width, 0, width, height)
        break
    }
  }

  /**
   * 压缩图片
   * @param img 图片
   * @param orientation orientation
   * @param quality 质量
   */
  public static compress (img: HTMLImageElement, orientation: number, quality = 0.8) {
    const canvas: HTMLCanvasElement = document.createElement('canvas')
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
    const initSize = img.src.length
    let width = img.width
    let height = img.height

    // 最长边控制到1024以下
    const max = 1024
    if (width > height) {
      if (width > max) {
        height = Math.floor(max / width * height)
        width = max
      }
    } else {
      if (height > max) {
        width = Math.floor(max / height * width)
        height = max
      }
    }

    canvas.width = width
    canvas.height = height
    // 铺底色
    ctx.fillStyle = '#fff'
    ctx.fillRect(0, 0, canvas.width, canvas.height)

    // 修复ios上传图片的时候 被旋转的问题
    if (orientation && orientation !== 1) {
      switch (orientation) {
        case 6: // 需要顺时针(向左)90度旋转
          ImageUtil.rotateImg(img, 90, canvas)
          break
        case 8: // 需要逆时针(向右)90度旋转
          ImageUtil.rotateImg(img, -90, canvas)
          break
        case 3: // 需要180度旋转
          ImageUtil.rotateImg(img, 180, canvas)
          break
      }
    } else {
      ctx.drawImage(img, 0, 0, width, height)
    }
    // 进行最小压缩
    const ndata = canvas.toDataURL('image/jpeg', quality)
    console.log('压缩前:' + initSize)
    console.log('压缩后:' + ndata.length)
    console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + '%')
    canvas.width = canvas.height = 0
    return ndata
  }
}
原文地址:https://www.cnblogs.com/binli/p/12800586.html