前言
以小程序 【识花君】为例子,分析下在小程序中如何实现拍照压缩上传。
一、camera 和 cover-view、cover-image 组件
首先分析如何实现类似的设计:
- 引用 camera 组件,并且通过样式设置宽高为全屏。(拓展:可以在样式中设置宽高,或者定位来调整相机组件在页面中的大小以及位置。)
- 以 cover-view 为父容器设置定位,以嵌套的 cover-image 引用图片
说明:
- 为什么不直接对 cover-image,而要使用 在外面嵌套一层 cover-view ?
答:因为对 cover-image 设置定位样式后,在真机上无效。基础库 1.9.90 起最外层 cover-view 支持 position: fixed 。 - cover-image 使用本地图片路径会存在问题
答:图标路径,支持临时路径、网络地址(1.6.0起支持)、云文件ID(2.2.3起支持)。暂不支持base64格式。
二、从相册选取
点击相册图标时,触发事件调用 wx.chooseImage(Object object)
即可。
三、拍照
- 图片 api :
wx.chooseImage(Object object)
- 相机 api :
CameraContext.takePhoto(Object object)
四、压缩
现在的智能手机拍出的照片,很容易达到 5M 左右,上传时不仅占用带宽,且速度慢。
小程序提供了 3 种方式可压缩图片:
- 选择照片时指定图片的尺寸或者拍照时指定成像质量
经过测试,如果对压缩要求比较高,这种方法是不行的,因为压缩效果不显著。 -
wx.compressImage(Object object)
局限性:仅对 jpg 有效。实际业务中包含png
、jpg
等多种格式。 - 通过 canvas 来曲线救国
原理:将一张大尺寸的图片通过 canvas 的提供的 drawImage()
方法绘制到小尺寸的画布上,再通过 canvasToTempFilePath
将画布内容生成图片,就完成了大尺寸到小尺寸的转换,完成了压缩。
步骤:
-
wx.getSystemInfoSync()
获取设备像素比 -
wx.chooseImg()
或者CameraContext.takePhoto
获取图片 -
wx.getImageInfo()
获取图片信息,并检测图片是否超过指定尺寸 -
drawImage()
绘制图片到画布 draw()
-
wx.canvasToTempFilePath()
将画布内容生成图片
说明:为什么需要设备像素比??
看下不处理设备像素比时,普通屏和二倍屏的对比,只关注 width
即可
在高倍屏上面,1px 对应的物理像素会比普通屏幕更多,这就导致通过 drawImage()
方法绘制时,虽然在 css
层面设置的宽高是一致的,比如(w: 300px),如果普通屏(pixelRatio: 1) 1px = 1 个物理像素,那么在二倍屏 (pixelRatio: 2) 上面 1 px = 4 个物理像素(宽是2, 高是2),所以实际上是将图片的宽绘制为 300 * 2
个物理像素,这时使用
canvasToTempFilePath()
生成图片的宽度是 600
, 而不是期望的 300
。
主要代码
模板 部分 (以下为 mpvue 中的语法)
<canvas class="canvas-hidden" :style="{width: cWidth + 'px', height: cHeight + 'px'}" canvas-id="CanvasId"/>
js 部分
pixelRatio 通过 wx.getSystemInfoSync()
获取。
// 将图片绘制到画布上
drawImage(file) {
const ctx = wx.createCanvasContext('CanvasId');
wx.getImageInfo({
src: file,
success: (res) => {
if (res.width > 300 || res.height > 300) { // 判断图片是否超过300像素
this.cWidth = 300 / this.pixelRatio;
this.cHeight = 300 / this.pixelRatio / scale;
// 画出压缩图片
ctx.drawImage(file, 0, 0, this.cWidth, this.cHeight);
ctx.draw();
setTimeout(() => {
this.canvasToImg();
}, 3000);
} else {
this.upload(file);
}
}
});
},
// 将画布内容转成图片
canvasToImg() {
wx.canvasToTempFilePath({
canvasId: 'CanvasId',
success: (res) => {
// 上传图片
this.upload(res.tempFilePath);
}
});
},
五、兼容性
- 小米 android 9.0 版本无法渲染出
https
协议的图片。
解决方案:前端强制转换成 http 。 - CanvasContext.draw(boolean reserve, function callback)
callback 在某些机型上面无效。(当前基础库2.6.5
)
解决方案:draw 之后强制 setTimeout 3s ,然后再去执行wx.canvasToTempFilePath(Object object, Object this)
2019/5/13 更改
解决方案:通过wx.getSystemInfoSync()
获取当前设备信息,其中的 platform
字段代表当前系统类型
- ios
CanvasContext.draw(false, () => {
wx.canvasToTempFilePath(Object object, Object this)
})
- android
draw 之后强制 setTimeout 3s ,然后再去执行wx.canvasToTempFilePath(Object object, Object this)
3: 对 canvas 应用样式 visibility
无效
解决方案: 通过 left: -9999;
或者 tranlateX()
改变位置,移至不可见区域。