logo头像
Snippet 博客主题

h5图片上传压缩坑点总结

本文于922天之前发表,文中内容可能已经过时。

背景

  • 公司目前有个h5的项目,需要嵌入到多个第三方的app里面。h5页面中有一个图片上传的功能,考虑到如果图片上传要让客户端做的话需要每个第三方app的客户端都实现一遍,所以最终决定由前端来实现。在实现过程中发现拍的照片非常大,往往有好几M,所以决定进行压缩。

大体思路

  • 通过FileReader将fie对象转换为data url形式
  • 新建一个canvas对象,通过canvas将图片画在这个画布中
  • 通过EXIF这个插件判断图片是否出现旋转问题,如果旋转了调整会正常状态
  • 通过canvas.toDataURL进行压缩
  • 将base64转换成Bolb对象实现上传

暂未解决的坑点

  • IOS8中FileReader转化为Base64的时候,字符串为空,所以在ios8的时候直接不压缩上传

具体实现

1
2
// 这里accept一定要是“image/*”
<input type="file" @change="handlerUpload" accept="image/*">
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
import { compressImg } from '../utils'
export default{
methods: {
handlerUpload(event){
const file = event.target.files[0]
const maxSize = 200 * 1024
let self = this
if(file.size < maxSize){
// 直接上传图片
self.uploadImg(file)
}
else {
// 先压缩再上传,如果压缩过程中出现问题,主要是机型兼容问题就直接不压缩上传
try{
compressImg(file, maxSize, (bolb) => {
const transFile = new File([bolb], file.name)
self.uploadImg(transFile)
})
}catch(e){
self.uploadImg(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
// utils 文件
import EXIF from 'exif-js'
/**
* 使用二进制方式处理dataUrl
* @param {*} dataUrl
*/
const convertBase64UrlToBlob = (dataUrl, callback) => {
const binaryString = window.atob(dataUrl.split(',')[1])
const arrayBuffer = binaryString.length
const intArray = new Uint8Array(arrayBuffer)
const imgType = dataUrl.match(/data:([^;]+)/)[1]
for (let i = 0, j = arrayBuffer; i < j; i++) {
intArray[i] = binaryString.charCodeAt(i)
}
const data = [intArray]
let blob = new Blob(data, {type: imgType})
callback(blob)
}
/**
* 修复某些机型出现旋转的问题
* @param {*} img
* @param {*} canvas
* @param {*} imgWidth
* @param {*} imgHeight
*/
const fixOrientation = (img, canvas, imgWidth, imgHeight) => {
let orientation
let ctx = canvas.getContext('2d')
EXIF.getData(img, function() {
orientation = EXIF.getTag(this, 'Orientation')
})
if(orientation && orientation != 1){
switch(orientation){
case 6: // 旋转90度
canvas.width = imgHeight
canvas.height = imgWidth
ctx.rotate(Math.PI / 2)
// (0,-imgHeight) 从旋转原理图那里获得的起始点
ctx.drawImage(img, 0, -imgHeight, imgWidth, imgHeight)
break
case 3: // 旋转180度
ctx.rotate(Math.PI);
ctx.drawImage(img, -imgWidth, -imgHeight, imgWidth, imgHeight)
break
case 8: // 旋转-90度
canvas.width = imgHeight
canvas.height = imgWidth
ctx.rotate(3 * Math.PI / 2)
ctx.drawImage(img, -imgWidth, 0, imgWidth, imgHeight)
break
}
}
else{
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
}
}
/**
* 使用canvas绘制图片并压缩
* @param {*} dataUrl 转换的二进制
* @param {*} imgType 图片类型
* @param {*} quality 图片压缩的比例
*/
const canvasDataURL = (dataUrl, imgType, quality = 0.8, callback) => {
let img = new Image()
let compressedDataUrl
img.src = dataUrl
img.onload = function(){
let w = this.width
let h = this.height
//生成canvas
let canvas = document.createElement('canvas')
canvas.width = w
canvas.height = h
// 修复图片旋转的问题
fixOrientation(this, canvas, w, h)
compressedDataUrl = canvas.toDataURL(imgType, Number(quality))
convertBase64UrlToBlob(compressedDataUrl, callback)
}
}
export const compressImg = (file, maxSize, callback) => {
const imgType = file.type || 'image/jpeg'
const ready = new FileReader()
ready.readAsDataURL(file)
ready.onload = function(e){
const { result } = e.target
// 去定压缩比例
const quality = ( maxSize / result.length).toFixed(2)
canvasDataURL(result, imgType, quality <= 1 ? quality : 1, callback)
}
}

⚠️部分API的兼容性请参照caniuse