在具体业务中,我们会遇到需要让用户上传本地图片的场景,随着现在的手机像素越来越高,图片的大小也越来越大,上传原图后一方面是难以上传成功,另一方面是上传成功后在列表中图片太大加载时间过长或者加载失败。若是直接提示用户 “无法上传xxM以上的图片” ,用户体验会不好,于是需要我们对用户上传的图片进行压缩。本文主要记录了开发过程中探索压缩图片的过程和方式,以及一些踩坑记录。
一、wx.chooseMedia
拍摄或从手机相册中选择图片或视频,wx.chooseMedia中有一个sizeType属性,选择上传原图还是缩略图
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/media/video/wx.chooseMedia.html
代码语言:javascript复制wx.chooseMedia({
count: 9,
mediaType: ['image'], // 只允许选择图片
sourceType: ['album', 'camera'], // 可以拍摄或从相册中选择
sizeType:['compressed'], // 选择压缩图
camera: 'back', // 后置摄像头
success(res) {
console.log(res)
}
})
优势:
- 这里的压缩和微信聊天和朋友圈中选择图片不勾选原图差不多的效果,省时省力(ps:当宽和高均小于1280,并且宽高比大于2时,微信聊天会话和微信朋友圈的处理不一样;朋友圈:取较小值等于1280,较大值等比例压缩;聊天会话:取较小值等于800,较大值等比例压缩 );
劣势:
- ios和安卓的压缩机制不同,安卓中一张1152*41549的长截图压缩后完全模糊,看不清;
- 另外安卓会出现压缩失效的情况,目前在华为p40、p40pro、mate30pro、mate40pro、Redmi K40、OPPO A55这些手机中发现此问题;
- 无法指定压缩质量。
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 786*1048 140KB | 1152*2376 515KB | 1152*41586 9.48MB | 1280*1706 715KB | 1280*1920 606KB | 1706*1280 648KB |
安卓 | 640*854 46.24KB | 1152*2376 209KB | 532*19188 1.2MB | 640*854 75.25KB | 640*960 93.56KB | 854*640 89.17KB |
二、wx.canvasToTempFilePath
绘制canvas,通过canvas.createImage将图片绘制上去,然后再使用wx.canvasToTempFilePath转为图片
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html
代码语言:javascript复制wx.canvasToTempFilePath({
width: 50, // 画布区域的宽度
height: 50, // 画布区域的高度
destWidth: 100, // 输出图片的宽度
destHeight: 100, // 输出图片的高度
canvasId: 'myCanvas',
quality: 1, // 图片质量0-1
success(res) {
console.log(res.tempFilePath)
}
})
优势:
- 可以自定义输出图片宽高和压缩质量;
劣势:
- 有宽高限制,根据具体机型的内存大小会有不同,一般建议不要超过4096,否则会绘制失败;
- ios和安卓的压缩机制不一致;
- 通过canvas转的图片会有略微色差,色彩没有原图明艳,如图:
quality为1:
786*1048 290KB | 1152*2376 1.07MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|
IOS | 786*1048 139KB | 1152*2376 472KB | 1280*1706 647KB | 压缩失败 | 1706*1280 610KB |
安卓 | 786*1048 298KB | 1152*2376 1.11MB | 1280*1706 1.77MB | 压缩失败 | 1706*1280 1.46MB |
quality为0.5:
786*1048 290KB | 1152*2376 1.07MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|
IOS | 786*1048 137KB | 1152*2376 467KB | 1280*1706 621KB | 压缩失败 | 1706*1280 593KB |
安卓 | 786*1048 47.44KB | 1152*2376 156KB | 1280*1706 260KB | 压缩失败 | 1706*1280 216KB |
三、wx.compressImage
通过控制压缩质量,输出图片
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.compressImage.html
代码语言:javascript复制wx.compressImage({
src: '', // 图片路径
quality: 80 // 压缩质量 0-100
})
优势:
- 不限图片宽高,任何图片都可以使用;
劣势:
- 只能压缩画质,不能压缩大小;
- ios和安卓压缩质量相同的情况下输出大小不一致,ios有自己的一套压缩机制,当压缩要极致质量大小以后,质量写再小都不会有变化;
- 开发工具压缩后返回的地址没有后缀;
- 在安卓中quality若是小于1,输出大小跟quality为80的一样。
quality为80:
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 139KB | 514KB | 9.46MB | 3.71MB | 8.34MB | 2.05MB |
安卓 | 75.87KB | 259KB | 6.11MB | 2.06MB | 3.49MB | 1.31MB |
quality为50:
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 137KB | 509KB | 8.01MB | 2.67MB | 5.51MB | 1.67MB |
安卓 | 47.47KB | 456KB | 3.78MB | 1.21MB | 1.63MB | 765KB |
quality为30:
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 132KB | 489KB | 6.73MB | 2.14MB | 3.42MB | 1.34MB |
安卓 | 35.81KB | 115KB | 2.84MB | 884KB | 0.99MB | 550KB |
以上图片在同质量压缩后,都还是很清晰
quality为10:
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 127KB | 468KB | 5.57MB | 1.66MB | 2.27MB | 2.23MB |
安卓 | 18.73KB | 56.47KB | 1.47MB | 390KB | 425KB | 260KB |
上面几张图同质量10压缩,1152*2376--1.07MB的压缩后很糊,图片越小压缩越糊
quality为1:
786*1048 290KB | 1152*2376 1.07MB | 1152*41586 21.45MB | 3072*4096 5.01MB | 3840*5760 19.55MB | 4032*3024 4.8MB | |
---|---|---|---|---|---|---|
IOS | 127KB | 468KB | 5.57MB | 1.66MB | 2.27MB | 2.23MB |
安卓 | 8.89KB | 25.52KB | 637KB | 119KB | 206KB | 107KB |
以上质量为1的情况下,安卓完全失真,色彩模糊,ios仍然保持自己的压缩极限值
四、综上所述
目前来看,暂时没有很完善的压缩图片方案,具体的还是得根据业务来。
我们这次的需求主要是用户上传图片,然后在列表中展示,现在手机像素都挺好的,拍摄的图片都很大,并且也会有用户上传长截图,因此列表中的图片加载很慢,所以我综合了以上三种方式来实现压缩图片:
- 判断系统为ios还是安卓;
- wx.chooseMedia的sizeType在ios中设置为['compressed']压缩图,利用好ios自带的压缩机制,安卓中设置为['original', 'compressed'],即原图和压缩图都可以,由用户选择,一方面利用好自带的压缩功能,另一方面如果图片宽高大于40000,安卓压缩后会超级模糊,这时候用户预览后可以自己选择重新上传原图;
- 选择图片以后,wx.chooseMedia返回的tempFiles中,会返回选择图片的大小,若是仍然大于我们限制的大小,则进行手动压缩;
- .wx.getImageInfo获取图片的宽高,若是有一边大于4096,则调用wx.compressImage强制压缩,若是小于4096,则绘制canvas实现压缩,设置压缩基础宽高为1280;
<!-- 将canvas挪出屏幕外 -->
<view style="position: fixed;top: -9999px;left: -9999px;">
<canvas type="2d" id='myCanvas{{item}}' style='border: 1px solid;' wx:for="{{bigImgs}}"></canvas>
</view>
代码语言:javascript复制 changeImage() {
// 图片限制大小
const fileLimit = 2 * 1024 * 1024
// 选择图片原图或是压缩图
const sizeType = this.data.isIos ? ['compressed'] : ['original', 'compressed']
wx.chooseMedia({
sizeType,
count: 9,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: async function (res) {
let tempFiles = res.tempFiles
if (tempFiles.length) {
for (let i = 0; i < tempFiles.length; i ) {
let filePath = tempFiles[i].tempFilePath
// 图片超过大小限制
if (tempFiles[i].size > fileLimit) {
// 手动压缩
filePath = await this.compressFile(filePath, i, tempFiles[i].size)
}
// 上传图片
wx.uploadFile({
url: 'xxx',
filePath,
name: 'xxx',
success: function (res) {
// 图片上传成功
},
fail: function () {
// 图片上传失败
}
})
}
}
}
})
},
// 压缩
compressFile(src, i, size) {
return new Promise((resolve) => {
// 获取图片信息
wx.getImageInfo({
src,
success: (img) => {
let imgWidth = img.width
let imgHeight = img.height
// 若宽高都小于4096,则使用canvas
if (imgWidth <= 4096 && imgHeight <= 4096) {
this.canvasToImg(src, i, imgWidth, imgHeight, size).then(res => {
resolve(res)
})
} else {
// 强制压缩
this.compressImage(src, size).then(res => {
resolve(res)
})
}
},
fail: () => {
this.compressImage(src, size).then(res => {
resolve(res)
})
}
})
})
},
// 绘制canvas
canvasToImg(src, i, imgWidth, imgHeight, size) {
return new Promise((resolve, reject) => {
const { pixelRatio, baseSize } = this.data // baseSize设为1280,与图片宽高做比较
let query = wx.createSelectorQuery().in(this)
query.select(`#myCanvas${i}`)
.fields({ node: true, size: true })
.exec((res) => {
let canvas = res[0].node
if (!canvas) {
// 强制压缩
this.compressImage(src, size).then(res => {
resolve(res)
})
return
}
let ctx = canvas.getContext('2d')
let pic = canvas.createImage()
pic.src = src
let canvasWidth = 0
let canvasHeight = 0
let quality = 1
// 图片宽和高都小于基础值,则宽高不变,压缩质量为0.3,这里的基础值设为1280
if (imgWidth <= baseSize && imgHeight <= baseSize) {
canvasWidth = imgWidth
canvasHeight = imgHeight
quality = .3
} else {
let compareFlag = true
// 手机宽高比大于2,图片一边大于基础值,一边小于基础值,则宽高不变,压缩质量为0.3
if (pixelRatio > 2 && (imgWidth < baseSize || imgHeight < baseSize) && (imgWidth > baseSize || imgHeight > baseSize)) {
canvasWidth = imgWidth
canvasHeight = imgHeight
quality = .3
} else {
// 手机宽高比大于2,宽高最小值设为基础值,另一边等比缩放,手机宽高比小于等于2,宽高最大值设为基础值,另一边等比缩放,压缩质量为0.9
compareFlag = pixelRatio > 2 ? (imgWidth > imgHeight) : (imgWidth < imgHeight)
canvasWidth = compareFlag ? parseInt(imgWidth / (imgHeight / baseSize)) : baseSize
canvasHeight = compareFlag ? baseSize : parseInt(imgHeight / (imgWidth / baseSize))
quality = .9
}
}
// 设置canvas宽高
canvas.width = canvasWidth
canvas.height = canvasHeight
pic.onerror = () => {
// 图片加载失败则继续强制压缩
this.compressImage(src, size).then(response => {
resolve(response)
})
}
pic.onload = () => {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.drawImage(pic, 0, 0, canvasWidth, canvasHeight)
wx.canvasToTempFilePath({
canvas,
quality,
fileType: 'jpg',
width: canvasWidth,
height: canvasHeight,
destWidth: canvasWidth,
destHeight: canvasHeight,
success: resp => {
// 生成的图片临时文件路径
resolve(resp.tempFilePath)
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
},
fail: () => {
this.compressImage(src, size).then(response => {
resolve(response)
})
}
})
}
})
})
},
// 强制压缩
compressImage(src, size) {
return new Promise((resolve, reject) => {
let quality = 100
// ios因为自己有压缩机制,压缩到极致就不会再压,因此往小了写
if (this.data.isIOS) {
quality = 0.1
} else {
let temp = 30 - parseInt(size / 1024 / 1024)
quality = temp < 10 ? 10 : temp
}
wx.compressImage({
src, // 图片路径
quality, // 压缩质量
success: function (res) {
resolve(res.tempFilePath)
},
fail: function (err) {
resolve(src)
}
})
})
},
优势:
- 利用好了程序自带的压缩功能;
- 图片宽高大于4096也可以压缩;
劣势:
- 在ios中,若是图片宽高大于4096,wx.chooseMedia压缩后仍然大于我们所限制的大小,则调用了wx.compressImage,若是这张图片在ios中压缩的极致值是30,那么我们设置quality为30以上是有压缩变化的,30以下他仍会按照30处理不会再压缩。
最后,以上方案仅供参考,具体的实现还要根据各种业务场景来操作哦,上面所有的测试数据安卓大多数是用的华为mate40pro,因此数据不是绝对的,机型不同可能存在差异。