图片压缩上传
# 基本流程
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);
该方法所接受的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);
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)
}
})
}
},
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
- 01
- 2023/07/03 00:00:00
- 02
- 2023/04/22 00:00:00
- 03
- 2023/02/16 00:00:00