微信小程序wxml-to-canvas保存图片时图片缺失(已解决)
原生微信小程序使用了多个生成海报保存到相册的插件,没有一个是不坑的
其中painter,会出现部分华为继续图片缺失,而且官方是GitHub,似乎2年都没更新了
最终选择了微信小程序官方推荐的wxml-to-canvas,生成海报保存相册
wxml-to-canvas问题:
1. 图片圆角时灵时不灵
2. 所有半透明背景代码失效, 半透明图片是黑色
3. 没有做海报的展示,让用户看到海报是否加载完毕,就直接保存到相册,海报如果体积太大,会出现保存的图片部分缺失
解决方法:
(更新根本解决方法: 先将所有域名返回的图片, 使用wx.downloadFile下载下来, 再重新汇总, 放入canvas标签结构中, 再执行canvas生成代码)
1. 圆角图片不要用代码设置,直接使用已生成的圆角图片贴上去
2. 放弃半透明背景代码, 直接让UI切个半透明的图, 底下要垫一张非半透明的图, 一起贴上去
3. 出现图片缺失,是因为用户网速或系统或图片域名等因素各不相同,影响了体积较大的图片加载未完成,就直接调用保存到相册,导致图片缺失,根本解决方法,始终只有一个,就是要捕捉到图片加载完毕,已全部显示出来,再保存到相册,我使用了很多方法,比如定时器延长时间,让图片加载,但定时器也不能完全满足,最终使用了微信小程序提供的image图片标签里的图片加载完毕事件,bindload事件,完美解决
代码如下:
wxml页面:
<!-- 显示相册授权的弹窗 -->
<van-overlay show="{{ isModal }}" bind:click="onClickHide"> 这个是vant的弹窗
<view class="wrapper">
<view class="block">
<text style="margin-top:46rpx;">温馨提示</text>
<text style="color:#666666;letter-spacing: 0;">请选择相册授权</text>
<view class="btns">
<button bindtap="onClickHide">取消</button>
<view></view>
<button open-type="openSetting" bindopensetting="callback">授权</button>
</view>
</view>
</view>
</van-overlay>
页面里的图片和海报,全部用定位给隐藏到边缘看不见,但实际上海报加载完毕的bindload依然会触发
sharePath => 海报地址是画布生成的地址
<!-- 海报图片 -->
<image class="canvas-img" src="{{sharePath}}" bindload="canvasImgLoad" style="height: 1505rpx;width: 750rpx;position: absolute; top: -9999rpx;"></image>
<!-- 生成的海报 -->
<wxml-to-canvas width="250" height="502" class="widget" style="position: absolute; top: -9999rpx;"></wxml-to-canvas>
上面 canvas 的 width 和 height, 可以控制海报的体积大小, 实际生成的海报像素比例, 是这里值的 3 倍
JS:
data: {
isModal: false, // 强制授权相册弹窗
canvasImgYes: false, // 海报图片是否加载完毕
allImgObj: {}, // canvas使用所有图片
},
// onLoad周期里绘制海报
onLoad(options) {
// 获取海报 canvas
this.widget = this.selectComponent('.widget')
},
// 整理下载的图片 => 最关键的就是这个方法, 一定要先将所有图片重新下载完毕, 再绘制到 canvas 上
async downLoadImg() {
let imgArr = [
{
label: 'qrCodeUrl',
url: this.data.shareObj.qrCodeUrl
},
{
label: 'topImg',
url: this.data.userInfo.topImg
},
{
label: 'contentBg',
url: this.data.shareObj.addressImageUrl
},
{
label: 'blueOp',
url: this.data.blueOp
},
{
label: 'centerPlaceRadius',
url: this.data.centerPlaceRadius || this.data.shareObj.addressImageUrl
},
]
let label = ''
imgArr.map(async item => {
// 使用 downloadFile 方法下载不同域名的图片
await wx.downloadFile({
url: item.url,
success: (res) => {
label = `allImgObj.${item.label}`
this.setData({
[label]: res.tempFilePath
})
if (Object.keys(this.data.allImgObj).length == imgArr.length) {
console.log(Object.keys(this.data.allImgObj).length);
// 当所有图片都下载完毕, 开始绘制海报
this.renderToCanvas()
}
}
})
})
},
//海报架构
async renderToCanvas() {
// 这里汇总canvas要渲染的所有图片和文字
let obj = {
qrCodeUrl: this.data.allImgObj.qrCodeUrl, // 二维码
topImg: this.data.allImgObj.topImg, // 头像
contentBg: this.data.allImgObj.contentBg, // 场地图片
blueOp: this.data.allImgObj.blueOp, // 蓝色渐变图
name: this.data.userInfo.name, // 姓名,
centerPlaceRadius: this.data.allImgObj.centerPlaceRadius, // 圆角图
placeName: this.data.placeName, // 名称
visitTime: this.data.shareObj.visitTime, // 时间
visitorsAddress: this.data.shareObj.visitorsAddress, // 地址
}
const wxmls = (info) => {
return `
<view class="container">
<image class="bg" src="${info.contentBg}" />
<image class="blueOp" src="${info.blueOp}" />
<view class="content">
<image class="topImg" src="${info.topImg}" />
<text class="name">${info.name}</text>
<text class="welcome">${info.placeName}</text>
<view class="bgCenter">
<image class="centerImg" src="${info.centerPlaceRadius}" />
</view>
<view class="nameBox">
<text class="placeName">${info.placeName}</text>
</view>
<view class="timePlaceBox">
<text class="timePlace">时间:${info.visitTime}</text>
</view>
<view class="timePlaceBox">
<text class="timePlace">地址:${info.visitorsAddress}</text>
</view>
<image class="qrCodeUrl" src="${info.qrCodeUrl}" />
<text class="hold">长按扫码</text>
</view>
</view>
`
}
// 传入要填入的动态数据
let wxml = wxmls(obj)
// 下方的尺寸全部除以 3 , 除以3之后的宽高, 等于wxml页面中的宽高值, 除以 3 之前的单位,
// 就是设计图的尺寸大小, 3 也是控制生成海报尺寸大小的, 越大则海报体积越小
const style = {
container: {
position: 'relative',
width: 750 / 3,
height: 1505 / 3,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
backgroundColor: '#5886FF',
},
bg: {
width: 750 / 3,
height: 489 / 3,
},
blueOp: {
position: 'absolute',
left: 0,
top: 0,
width: 750 / 3,
height: 489 / 3,
},
content: {
position: 'relative',
width: 702 / 3,
height: 1205 / 3,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#FFFFFF',
borderRadius: '15',
marginTop: -224 / 3,
},
topImg: {
position: 'absolute',
width: 80 / 3,
height: 80 / 3,
top: 64 / 3,
left: 30 / 3,
borderRadius: 40 / 3,
},
name: {
position: 'absolute',
width: 200 / 3,
height: 64 / 3,
top: 60 / 3,
left: 130 / 3,
fontWeight: 600,
color: '#000000',
fontSize: 32 / 3,
fontFamily: 'PingFangSC-Medium',
},
welcome: {
position: 'absolute',
width: 500 / 3,
height: 28 * 3,
fontSize: 28 / 3,
color: '#666666',
fontWeight: 400,
left: 130 / 3,
top: 107 / 3,
fontFamily: 'PingFangSC-Regular',
},
bgCenter: {
width: 642 / 3,
height: 274 / 3,
position: 'absolute',
top: 177 / 3,
left: 30 / 3,
},
centerImg: {
width: 642 / 3,
height: 274 / 3,
},
nameBox: {
width: 340 / 3,
height: 80/ 3,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginTop: 60 / 3,
},
placeName: {
width: 240 / 3,
height: 80 / 3,
fontSize: 40 / 3,
fontWeight: 500,
color: '#000000',
fontFamily: 'PingFangSC-Medium',
},
timePlaceBox: {
width: 542/ 3,
height: 64 / 3,
},
timePlace: {
width: 542/ 3,
height: 64 / 3,
fontSize: 32 / 3,
fontWeight: 400,
color: '#666666',
fontFamily: 'PingFangSC-Regular',
},
qrCodeUrl: {
width: 300 / 3,
height: 300 / 3,
position: 'absolute',
top: 757 / 3,
left: 206 / 3,
},
hold: {
width: 220 / 3,
height: 52 / 3,
fontSize: 26 / 3,
color: '#666666',
position: 'absolute',
top: 1062 / 3,
left: 247 / 3,
fontFamily: 'PingFangSC-Regular',
}
}
const p1 = this.widget.renderToCanvas({ wxml, style })
p1.then((res) => {
// 该方法是开始生成海报链接的方法
this.createImage()
}).catch(err => {
// 下方的提示, 就是如果onLoad中的方法报错, 给的提示, 一般重新进入页面, 就不会出错
wx.showToast({
title: '加载失败,请重新进入页面',
icon: 'none'
})
})
},
// 生成海报链接
createImage() {
const p2 = this.widget.canvasToTempFilePath()
p2.then(res => {
// 这就是海报的链接
// console.log(res.tempFilePath)
this.setData({
sharePath: res.tempFilePath
})
}).catch(err=>{
wx.showToast({
title: '加载失败,请重新进入页面',
icon: 'none'
})
})
},
// 页面中海报图片加载完毕回调
canvasImgLoad() {
console.log('海报图片加载完毕');
this.setData({
canvasImgYes: true // 用来判断海报是否已加载渲染完毕
})
},
//点击保存按钮 该方法给页面中的保存到相册按钮
extraImage() {
if (this.data.canvasImgYes) { // 判断是否加载完毕的变量
// 如果还是有图片缺失, 就在这里加个定时器如 2 秒后再执行下方方法
this.savePhotos(this.data.sharePath)
} else {
wx.showToast({
title: '正在生成海报,请等待~',
icon: 'none',
duration: 3000, //持续的时间
success: res => {
this.extraImage() // 还没加载完就调自己继续判断
}
})
}
},
// 保存到相册的方法
savePhotos(path) {
wx.saveImageToPhotosAlbum({
filePath: path,
success(res) {
wx.hideLoading()
wx.showToast({
title: '海报生成成功,已保存到相册',
icon: 'none'
})
},
fail: (res) => {
wx.getSetting({
success: res => {
let authSetting = res.authSetting
if (!authSetting['scope.writePhotosAlbum']) { // 如果没有授权相册, 弹窗授权相册
wx.hideLoading()
this.setData({
isModal: true, // 控制是否弹窗强制授权的变量
})
}
}
})
}
})
},
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)