上传图片是小程序常见的功能,例如点评类小程序邀请用户分享照片、电商类小程序要求商家上传商品照片。
伴随着照片像素越来越高,图片体积越来越大,小程序开发者需要压缩图片,否则将导致用户上传图片失败或加载时间过长等影响体验的情况。
小程序提供 wx.chooseMedia、wx.canvasToTempFilePath、wx.compressImage 3 个图片类接口,便于开发者在不同应用场景下处理图片。除此以外,这 3 个接口的巧妙结合能够满足更多元化的图片压缩需求。下面就来看看怎样使用吧!
wx.chooseMedia 支持在使用小程序过程中拍摄或从手机相册选择图片或视频,其 sizeType 属性支持是否上传缩略图。该接口应用简便,接入即可实现压缩图片效果,省时省力。
wx.chooseMedia({
count: 9,
mediaType: ['image'], // 只允许选择图片
sourceType: ['album', 'camera'], // 可以拍摄或从相册中选择
sizeType:['compressed'], // 选择压缩图
camera: 'back', // 后置摄像头
success(res) {
console.log(res)
}
});
然而,该接口在压缩图片方面也有一定的限制:
无法指定压缩质量
部分安卓机型存在压缩失效的情况
iOS 和安卓的压缩机制不同,需要进行合理兼容
开发者可以通过控制 Canvas.createImage
绘制图片到 Canvas,然后利用 wx.canvasToTempFilePath
接口转换成图片。
这种方法能够高效控制图片宽高尺寸以及压缩质量,非常适用于有图片要求的场景。
wx.canvasToTempFilePath({
width: 50, // 画布区域的宽度
height: 50, // 画布区域的高度
destWidth: 100, // 输出图片的宽度
destHeight: 100, // 输出图片的高度
canvasId: 'myCanvas',
quality: 1, // 图片质量0-1
success(res) {
console.log(res.tempFilePath)
}
});
但是这种方式也会存在一定的限制:
iOS 和安卓的压缩机制不同,需要进行合理兼容
通过 Canvas 转换的图片存在略微色差
开发者可以调用wx.compressImage
接口直接压缩图片,而且支持选择压缩质量,不限制图片宽高尺寸,非常适用于处理特殊大小的图片。
wx.compressImage({
src: '', // 图片路径
quality: 80 // 压缩质量 0-100
});
同时这种方式也需要考虑不同系统的压缩差异:
在压缩到极限值时,iOS 压缩图画质不会随着压缩质量变小而变化
在压缩质量小于 1 时,安卓系统输出的画质将不再变小
回顾常见的小程序业务场景,图片处理主要聚焦于用户上传图片、列表展示这 2 个环节,可以结合以上 3 个接口实现最佳图片处理方式,既能够利用接口自带的压缩功能,省时省力;又能够解决图片太大造成的压缩难题。
判断当前系统是 iOS 系统还是安卓系统
function isIOS(){
return wx.getSystemInfo().then(res => {
return /IOS/ig.test(res.system);
});
}
iOS 系统:设置 sizeType 为 [‘compressed’],利用 iOS 压缩体系自动压缩
安卓系统:设置 sizeType 为 [‘original’, ‘compressed’],让用户自主选择上传原图或压缩图。
这种方式一方面利用接口自带的压缩能力; 另一方面如果图片宽高大于安卓能清晰压缩的值(例如40000),用户会预览到比较模糊的照片而选择上传原图
当用户选择图片后,wx.chooseMedia
返回的 tempFiles 显示对应图片的大小。如果该图片大小大于限制值,则进行手动压缩。
通过 wx.getImageInfo
获取图片的宽高:
如果宽度或高度大于 4096,调用 wx.compressImage
强制压缩
如果宽度和高度都小于 4096,绘制 Canvas 实现压缩,设置压缩基础宽高为 1280
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)
}
})
})
},
每种图片处理方式都有其突出的优势,结合多种方式能够最优地解决问题,适用于目标场景,便利用户上传图片的体验。