不喜欢看废话的小伙伴,可以直接看图片下面的文字和注意事项 实现是没问题的。
应用场景:
项目中,每个用户都会生成自己专属的小程序码,当用户扫描这个小程序码的时候,默认这个用户就会成为你的好友,,嗯,举个身边的例子,比如现在最火的 [邀好友领红包],通过扫描二维码确定绑定关系。(不要纠结为啥放这张图片,因为刚好昨天我妹妹发我,让我给她扫一扫,🌰栗子放在这里 感觉会更加清楚一点,有点广告嫌疑 哈哈哈哈 可以忽略 )。
在小程序中,我们可以生成带参的小程序码,但是呢,这个小程序码是一张小程序码图片,如果想要和产品放在一张图上,前端不做,后端做,反正总要有人做,然后,,决定让前端来做。ok,动起来,开始干。
结合文档和度娘,发现可以用canvas
来实现,发现用两个函数就可以搞定, CanvasContext.drawImage
画出你想画的图片
wx.canvasToTempFilePath
把画布的内容输出为图片
实际效果:
两张独立的图片,根据需要绘制在指定位置,合成一张图片,并保存到相册。
注意事项:
CanvasContext.drawImage
drawImage(imageResource, dx, dy)
drawImage(imageResource, dx, dy, dWidth, dHeight)
drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) 从1.9.0 起支持
imageResource
,绘制的图片的路径,必须是本地图片,如果是网络图片,必须用wx.downloadFile获取本地图片的缓存后再绘制。
dx、dy
图片的位置,画布左上角为原点(0,0),注意是画布的坐标原点而不是以手机屏幕为准。
dWidth, dHeight
绘制图片的宽高,即你想把这张图片画多大,如果想要绘制原图,则通过 wx.getImageInfo
获取图片的尺寸信息,直接进行绘制就可以了。
特别注意,CanvasContext.drawImage中的dx, dy, dWidth, dHeight
都是以像素px为单位的,所以不要用 rpx !!(我踩过的坑)
wx.canvasToTempFilePath
对这个函数,我的理解就是 手机截屏,截的是画布中的内容,画布多大它就多大,嗯,说到这,你应该就知道怎么控制底图大小,然后还很完美没有多余的白边。
代码:
可以直接用的代码,不过还是建议自己写下,也许写的过程还会发现其他有趣的问题呢。
// 保存图片到相册
savePhoto: function(index) {
let that = this
let imgs = that.data.imgs
if (imgs[index].isDownLoad) {
// 如果已经下载过 提示用户
wx.showToast({
title: '你已经下载过该图片',
icon: 'none'
})
return
} else {
// 提示用户正在合成,否则用户可能有不当操作或者以为手机卡死
wx.showLoading({
title: '合成中......',
mask: true
})
// 创建画布对象
const ctx = wx.createCanvasContext("myCanvas", that)
// 获取图片信息,要按照原图来绘制,否则图片会变形
wx.getImageInfo({
src: that.data.imgs[index].img,
success: function(res) {
// 根据 图片的大小 绘制底图 的大小
console.log(" 绘制底图 的图片信息》》》", res)
let imgW = res.width
let imgH = res.height
let imgPath = res.path
that.setData({
canvasHeight: imgH,
canvasWidth: imgW
})
// 绘制底图 用原图的宽高比绘制
ctx.drawImage(imgPath, 0, 0, imgW, imgH)
wx.getImageInfo({
src: that.data.codeImg, // 二维码图片的路径
success: function(res) {
console.log(" 绘制二维码》》》", res)
// 绘制二维码
ctx.drawImage(res.path, 50, imgH - 380, 300, 300)
ctx.draw()
wx.showLoading({
title: '正在保存',
mask: true
})
setTimeout(() => {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: function(res) {
console.log("合成的带有小程序码的图片success》》》", res)
let tempFilePath = res.tempFilePath
// 保存到相册
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success(res) {
// 修改下载状态
imgs[index].isDownLoad = true
wx.hideLoading()
wx.showModal({
title: '温馨提示',
content: '图片保存成功,可在相册中查看',
showCancel: false,
success(res) {
wx.clear
if (res.confirm) {
that.setData({
isShow: true
})
}
}
})
that.setData({
imgs: imgs,
})
},
fail(res) {
wx.hideLoading()
wx.showModal({
title: '温馨提示',
content: '图片保存失败,请重试',
showCancel: false
})
}
})
console.log("合成的带有小程序码的图片的信息》》》", res)
},
fail: function(res) {
console.log("生成的图拍呢 失败 fail fail fail ", res)
wx.hideLoading()
wx.showModal({
title: '温馨提示',
content: '小程序码图片合成失败,请重试',
showCancel: false
})
}
}, that)
},1500)
},
fail(res) {
wx.hideLoading()
wx.showModal({
title: '温馨提示',
content: '二维码获取失败,请重试',
showCancel: false
})
}
})
},
fail(res) {
wx.hideLoading()
wx.showModal({
title: '温馨提示',
content: '图片信息获取失败,请重试',
showCancel: false
})
}
})
}
},
wxml:
<view class="placeHoder"></view>
<canvas style="width:{{canvasWidth}}px; height:{{canvasHeight}}px;" canvas-id="myCanvas" />
json
禁止页面滑动
"disableScroll":true
真良心,xml也给贴出来啦。isShow 是因为我想直接保存,不给用户看到绘制的过程,如果你那里可以预览的话,简直完美,不需要隐藏绘制过程。
一点曲折
需求:
用户看到的是一张不包含二维码的缩略图,当用户点击之后 会执行下载操作,把包含二维码的原图保存到手机相册中,用户不能看到绘制过程,
解决思路:
原先的处理是 用hidden来控制canvas的显示隐藏,由于原图比较大 所以会出现页面滑动出现大量空白页面的问题,并且 有可能绘制不出图片,有时候会出现小程序意外退出的问题。
后来 我这里直接 用一个view和缩略图占据全屏,然后 在看不到的地方绘制原图和二维码,绘制完成后进行保存。
问题:
IOS: canvasToTempFilePath:fail. errMsg:"canvasToTempFilePath:fail no image"
Android:canvasToTempFilePath:fail. errMsg:"canvasToTempFilePath:fail create bitmap failed"
这个问题 是由于对canvas 用了hidden 属性,导致绘图失败,本来是打算用用hidden来隐藏绘制过程的,前期没有问题,后来出现绘制失败 无法保存合成的图片,甚至小程序意外退出的情况,因此,canvas绘制在屏幕外,禁止屏幕滑动来隐藏绘制过程。
另外一个需要注意的问题是 当点击图片的时候会进行保存,如果用户连续点击多次则会出现卡死退出的情况,因为绘制的是原图,比较大,所以当正在绘制的时候要控制不能多次下载绘制。可以用一个变量来控制。
IOS 保存多张图片的时候会出现内存不足,小程序退出的情况,因为在绘制过程中会先缓存图片然后进行绘制,当图片过多的时候会出现这种情况,需要在微信中 设置==> 存储 ==> 缓存 清除缓存 再次进行存储。
嗯,惯例,写给自己一句话,好好爱自己呀。