在具体业务中,我们会遇到需要让用户上传本地图片的场景,随着现在的手机像素越来越高,图片的大小也越来越大,上传原图后一方面是难以上传成功,另一方面是上传成功后在列表中图片太大加载时间过长或者加载失败。若是直接提示用户 “无法上传xxM以上的图片” ,用户体验会不好,于是需要我们对用户上传的图片进行压缩。本文主要记录了开发过程中探索压缩图片的过程和方式,以及一些踩坑记录。
拍摄或从手机相册中选择图片或视频,wx.chooseMedia中有一个sizeType属性,选择上传原图还是缩略图
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/media/video/wx.chooseMedia.html
wx.chooseMedia({
count: 9,
mediaType: ['image'], // 只允许选择图片
sourceType: ['album', 'camera'], // 可以拍摄或从相册中选择
sizeType:['compressed'], // 选择压缩图
camera: 'back', // 后置摄像头
success(res) {
console.log(res)
}
})
优势:
劣势:
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 |
绘制canvas,通过canvas.createImage将图片绘制上去,然后再使用wx.canvasToTempFilePath转为图片
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html
wx.canvasToTempFilePath({
width: 50, // 画布区域的宽度
height: 50, // 画布区域的高度
destWidth: 100, // 输出图片的宽度
destHeight: 100, // 输出图片的高度
canvasId: 'myCanvas',
quality: 1, // 图片质量0-1
success(res) {
console.log(res.tempFilePath)
}
})
优势:
劣势:
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 |
通过控制压缩质量,输出图片
API官方链接:https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.compressImage.html
wx.compressImage({
src: '', // 图片路径
quality: 80 // 压缩质量 0-100
})
优势:
劣势:
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仍然保持自己的压缩极限值
目前来看,暂时没有很完善的压缩图片方案,具体的还是得根据业务来。
我们这次的需求主要是用户上传图片,然后在列表中展示,现在手机像素都挺好的,拍摄的图片都很大,并且也会有用户上传长截图,因此列表中的图片加载很慢,所以我综合了以上三种方式来实现压缩图片:
<!-- 将canvas挪出屏幕外 -->
<view style="position: fixed;top: -9999px;left: -9999px;">
<canvas type="2d" id='myCanvas{{item}}' style='border: 1px solid;' wx:for="{{bigImgs}}"></canvas>
</view>
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)
}
})
})
},
优势:
劣势:
最后,以上方案仅供参考,具体的实现还要根据各种业务场景来操作哦,上面所有的测试数据安卓大多数是用的华为mate40pro,因此数据不是绝对的,机型不同可能存在差异。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。