一次使用 Canvas 压缩图片的经历

494 阅读1分钟

起因

ant-design 的 uploder 在进行图片裁切后可能尺寸变大。蚂蚁官方推荐是使用 ant-img-crop,这个是基于 react-easy-crop 进行了二次的封装的一个库。通过查看源码,这个裁剪图片其实也是基于 canvas 进行重新绘制,同时库里并没有对图片的尺寸进行限制。

解决

裁剪后图片大小不符合预期 #147

是否能确保剪裁后图片大小不变? #188

官方有相关issue,推荐自行压缩处理。

图片大小变大的原因

目前可复现的是上传图片源尺寸过大,经过裁剪后图片大小变大了

在满足需求且保证清晰度的情况下。我们选择使用二倍图视窗等比压缩,以iPhone X为标准的视窗大小

在图片源尺寸长/宽大小超过二倍图视窗尺寸时,进行等比压缩。否则不压缩。

// 二倍图尺寸
const DEFAULT_MEDIA_SIZE = {
  width: 750,
  height: 1624,
}

const getMediaSizeBySource = (sourceSize: {
  width: number
  height: number
}) => {
  // 超过二倍图尺寸等比缩放
  if (
    sourceSize.width > DEFAULT_MEDIA_SIZE.width ||
    sourceSize.height > DEFAULT_MEDIA_SIZE.height
  ) {
    const zoom =
      sourceSize.width > DEFAULT_MEDIA_SIZE.width
        ? DEFAULT_MEDIA_SIZE.width / sourceSize.width
        : DEFAULT_MEDIA_SIZE.height / sourceSize.height
    return {
      width: sourceSize.width * zoom,
      height: sourceSize.height * zoom,
    }
  }

  return sourceSize
}

我们使用 canvas 进行等比绘制

const compressFileByCanvas = async (file: RcFile) => {
  const canvas = document.createElement('canvas') as HTMLCanvasElement
  const ctx = canvas.getContext('2d')
  const reader = new FileReader()
  reader.readAsDataURL(file as File)
  const img = new Image()
  reader.onloadend = () => {
    img.src = reader.result as string
  }
  img.onload = () => {
    const { width, height } = img
    const mediaSize = getMediaSizeBySource({ width, height })
    canvas.width = mediaSize.width
    canvas.height = mediaSize.height
    ctx?.drawImage(img, 0, 0, mediaSize.width, mediaSize.height)
  }
}

进行绘制后,我们需要将画布内容转换成文件供 antd uploader 使用

  const canvasToFile = (
    canvas: HTMLCanvasElement,
    quality: number,
    type: string,
  ): Promise<Blob> =>
    new Promise(resolve => {
      canvas.toBlob(blob => resolve(blob as Blob), type, quality)
    })
    
  const file = await canvasToFile(canvas, 1, file.type)