loading

图片压缩上传

# 基本流程

input 读取到 文件 ,使用 FileReader 将其转换为 base64 编码 新建 img ,使其 src 指向刚刚的 base64 新建 canvas ,将 img 画到 canvas 上 利用 canvas.toDataURL/toBlob 将 canvas 导出为 base64 或 Blob 将 base64 或 Blob 转化为 File

# FileReader

FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或 Blob对象指定要读取的文件或数据。 通俗来讲,就是这个对象是用来读取File对象或Blob对象的。File对象就是<input type="file">获取到的对象

  • FileReader.onload:处理load事件。即该钩子在读取操作完成时触发,通过该钩子函数可以完成例如读取完图片后进行预览的操作,或读取完图片后对图片内容进行二次处理等操作。
  • FileReader.readAsDataURL:读取方法,并且读取完成后,result属性将返回 Data URL 格式(Base64 编码)的字符串,代表图片内容。
  • 除了用到的这个钩子和这个实例方法外,FileReader对象还有onabort、onerror、onloadstart、onloadend、onprogress等钩子;也有abort()、readAsArrayBuffer、readAsBinaryString等实例方法

# canvas

图片压缩最核心的在canvas这里,先使用CanvasRenderingContext2D.drawImage()方法将选中的图片文件在画布上绘制出来,再使用Canvas.toDataURL()将画布上的图片信息转换成base64(DataURL)格式的数据

  • CanvasRenderingContext2D.drawImage():CanvasRenderingContext2D上的绘制图片的方法
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
1

该方法所接受的9个参数:

  • image:Object;绘制在Canvas上的元素,可以是各类Canvas图片资源),如图片,SVG图像,Canvas元素本身等。
  • dx:Number;在Canvas画布上规划一片区域用来放置图片,dx就是这片区域的左上角横坐标。
  • dy:Nmuber;在Canvas画布上规划一片区域用来放置图片,dy就是这片区域的左上角纵坐标。
  • dWidth:Number;在Canvas画布上规划一片区域用来放置图片,dWidth就是这片区域的宽度。
  • dHeight:Number;在在Canvas画布上规划一片区域用来放置图片,dHeight就是这片区域的高度。
  • sx:Number;表示图片元素绘制在Canvas画布上起始横坐标。
  • sy:Number;表示图片元素绘制在Canvas画布上起始纵坐标。
  • sWidth:Number;表示图片元素从坐标点开始算,多大的宽度内容绘制Canvas画布上。
  • sHeight;表示图片元素从坐标点开始算,多大的高度内容绘制Canvas画布上。

实际上只使用到了5个参数

# Canvas.toDataURl() 图片压缩的核心。

canvas.toDataURL(mimeType, quality);
1

Canvas.toDataURl()方法可以将canvas画布上的信息转换为base64(DataURL)格式的图像信息,纯字符的图片表示形式。该方法接收2个参数:

  • mimeType(可选):String;表示需要转换的图像的mimeType类型。默认值是image/png,还可以是image/jpeg,甚至image/webp(前提浏览器支持)等。
  • quailty(可选):Number;quality表示转换的图片质量。范围是0到1。此参数要想有效,图片的mimeType需要是image/jpeg或者image/webp,其他mimeType值无效。默认压缩质量是0.92。

该方法为同步方法,如果需要转换的Canvas尺寸很大,则会阻塞脚本的运行,因此需要注意控制Canvas的尺寸

这个默认值得到的图片往往比原图的图片质量要高,图片往往比原图大。

当quality在0.2~0.5之间,图片质量变化并不大,quality的值越小,压缩效率越可观(也就是在0.2左右时,压缩图片可以最大化,同时并不对图片质量造成太大影响)

  • 另外,图片的尺寸也会影响,一个14M的原图(6016X4016)不改变质量只改变尺寸的前提下压缩后(1400X935)就剩139.62KB,尺寸的限制能最大化提高压缩效率

  • Canvas.toBlob():canvas.toBlob(callback, mimeType, quality)

将canvas画布上的信息转换为Blob对象。该方法接收的参数基本与toDataURL()的方法相同,区别在于,该方法多接受一个参数,该参数为:

  • callback:Function;toBlob()方法执行成功后的回调方法,支持一个参数,表示当前转换的Blob对象。 toDataURL()方法是同步方法,toBlob()不同,它是一个异步的方法,所以该方法会多接受一个参数callback,该参数就是toBlob()的回调函数。如果后端只接受二进制的图片信息的话,将file压缩后,再转换成Blob对象上传至后端

# 代码


  /**
   * 压缩图片方法
   * @param {file} file 文件
   * @param {Number} quality 图片质量(取值0-1之间默认0.92)
   */
  compressImg(file, quality) {
    var qualitys = 0.52
    console.log(parseInt((file.size / 1024).toFixed(2)))
    if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
      qualitys = 0.85
    }
    if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
      qualitys = 0.92
    }
    if (quality) {
      qualitys = quality
    }
    if (file[0]) {
      return Promise.all(Array.from(file).map(e => this.compressImg(e,
        qualitys))) // 如果是 file 数组返回 Promise 数组
    } else {
      return new Promise((resolve) => {
        console.log(file)
        if ((file.size / 1024).toFixed(2) < 300) {
          resolve({
            file: file
          })
        } else {
          const reader = new FileReader() // 创建 FileReader
          reader.onload = ({
            target: {
              result: src
            }
          }) => {
            const image = new Image() // 创建 img 元素
            image.onload = async() => {
              const canvas = document.createElement('canvas') // 创建 canvas 元素
              const context = canvas.getContext('2d')
              var targetWidth = image.width
              var targetHeight = image.height
              var originWidth = image.width
              var originHeight = image.height
              if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
                var maxWidth = 1600
                var maxHeight = 1600
                targetWidth = originWidth
                targetHeight = originHeight
                // 图片尺寸超过的限制
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // 更宽,按照宽度限定尺寸
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
                maxWidth = 1400
                maxHeight = 1400
                targetWidth = originWidth
                targetHeight = originHeight
                // 图片尺寸超过的限制
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // 更宽,按照宽度限定尺寸
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              canvas.width = targetWidth
              canvas.height = targetHeight
              context.clearRect(0, 0, targetWidth, targetHeight)
              context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
              const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
              const buffer = atob(canvasURL.split(',')[1]) // window.atob() 是解密 base64 的方法
              let length = buffer.length
              const bufferArray = new Uint8Array(new ArrayBuffer(length))
              while (length--) {
                bufferArray[length] = buffer.charCodeAt(length)
              }
              const miniFile = new File([bufferArray], file.name, {
                type: 'image/jpeg'
              })
              console.log({
                file: miniFile,
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2)),
                qualitys: qualitys
              })
              resolve({
                file: miniFile, // 要上传的压缩后的图片
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2))
              })
            }
            image.src = src
          }
          reader.readAsDataURL(file)
        }
      })
    }
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
最近更新时间: 2022/09/28 16:26:36
最近更新
01
2023/07/03 00:00:00
02
2023/04/22 00:00:00
03
2023/02/16 00:00:00