整体思路
- 获取手机屏幕大小去依据设计尺寸比例调整 —
wx.getSystemInfo
- 网络图片、base64图片保存到到本地临时文件路径
- canvas绘制图片 —
wx.createCanvasContext
- 获取用户相册权限 —
wx.getSetting
- canvas海报保存到本地临时文件路径 —
canvasToTempFilePath
- canvas图片保存到本地相册 —
saveImageToPhotosAlbum
效果图
代码示例
wxml
<block wx:if="{{canvasType}}">
<canvas class="canvas" canvas-id="shareCanvas" bindtouchstart="start" bindtouchmove="move" bindtouchend="end"></canvas>
</block>
<view bindtap="shareBtn">保存分享图片</view>
js
// 点击保存图片按钮
shareBtn() {
setTimeout(() => {
this.getSysInfo()
}, 200)
},
//获取图片信息和屏幕尺寸
getSysInfo() {
let that = this
let bgImgUrl = this._mapHttpToHttps(that.data.canvasInfoData.posterUrl)
let iconUrl = this._mapHttpToHttps(that.data.canvasInfoData.avatar)
wx.getSystemInfo({
success(res) {
that.setData({
canvasWidth: res.windowWidth,
canvasHeight: res.windowHeight
})
that.getImginfo([bgImgUrl, iconUrl], 0);
}
})
},
// 获取图片信息
getImginfo(urlArr, _type) {
let that = this;
wx.getImageInfo({
src: urlArr[_type], //服务器返回的带参数的小程序码地址
success: function (res) {
//res.path是网络图片的本地地址
if (_type === 0) { //背景图片
that.setData({
bgImgUrl: res.path,
})
that.getImginfo(urlArr, 1)
} else { //头像图片
that.setData({
iconUrl: res.path,
loadType: true,
iconWidth: res.width,
iconHeight: res.height,
})
// 创建canvas图片
that.canvasImg();
}
},
fail: function (res) {
//失败回调
console.log('错误-res', _type, res)
}
});
},
// 将http转为https
_mapHttpToHttps(rawUrl) {
if (rawUrl.indexOf(":") < 0) {
return rawUrl
}
const urlCompnent = rawUrl.split(":")
if (urlCompnent.length === 2) {
if (urlCompnent[0] === 'http') {
urlCompnent[0] = 'https'
return `${urlCompnent[0]}:${urlCompnent[1]}`
}
}
return rawUrl
},
生成图片 绘制canvas
canvasImg() {
let that = this;
wx.showLoading({
title: '图片正在生成'
});
var x = this.data.canvasWidth / 750; //设置相对canvas自适应根元素大小
const ctx = wx.createCanvasContext('shareCanvas', this);
ctx.drawImage(this.data.bgImgUrl, 0, 0, 750 * x, 1334 * x);
//方形二维码框
ctx.save();
ctx.fillStyle = "#fff";
ctx.fillRect(560 * x, 1144 * x, 160 * x, 160 * x);
ctx.drawImage(this.data.base64Url, 570 * x, 1155 * x, 140 * x, 140 * x);
ctx.restore();
//圆形头像框
//圆形头像框
ctx.save();
ctx.beginPath();
ctx.arc(150 * x, 800 * x, 90 * x, 0, 2 * Math.PI);
ctx.clip();
ctx.fillStyle = "#fff";
//头像
// ctx.drawImage(this.data.iconUrl, 60 * x, 710 * x, 180 * x, 180 * x);
//图片长宽
let h = this.data.iconHeight
let w = this.data.iconWidth
//宽高比
let dw = 180 / this.data.iconWidth
let dh = 180 / this.data.iconHeight
// 裁剪图片中间部分
if (w > 180 && h > 180 || w < 180 && h < 180) {
if (dw > dh) {
ctx.drawImage(this.data.iconUrl, 0, (h - 180 / dw) / 2, w, 180 / dw, 60 * x, 710 * x, 180 * x, 180 * x)
} else {
ctx.drawImage(this.data.iconUrl, (w - 180 / dh) / 2, 0, 180 / dh, h, 60 * x, 710 * x, 180 * x, 180 * x)
}
}
// 拉伸图片
else {
if (w < 180) {
ctx.drawImage(this.data.iconUrl, 0, (h - 180 / dw) / 2, w, 180 / dw, 60 * x, 710 * x, 180 * x, 180 * x)
} else {
ctx.drawImage(this.data.iconUrl, (w - 180 / dh) / 2, 0, 180 / dh, h, 60 * x, 710 * x, 180 * x, 180 * x)
}
}
ctx.restore();
ctx.beginPath()
ctx.moveTo(0, 1010 * x);
ctx.lineTo(750 * x, 1010 * x);
ctx.strokeStyle = "#F0F5F8";
ctx.stroke();
ctx.closePath();
//名字
ctx.save();
ctx.fillStyle = '#3C3C3C';
ctx.font = 'normal bold 17px sans-serif';
ctx.fillText(`${this.data.canvasInfoData.name}`, 272 * x, 790 * x);
ctx.restore();
//等等XXXXXXXXXXXXXXXXXXX
ctx.save()
let textLength = ctx.measureText(this.data.currentTask).width
// 长度按照文字长度计算 218 * x
let rectX = (708 * x) - textLength - 5 //476 * x
that.draw(ctx, rectX, 848 * x, textLength + 10, 43 * x, 5, '#fff', '#008CD6')
ctx.restore();
// 参与人数
ctx.save()
that.draw(ctx, 20 * x, 40 * x, 240 * x, 55 * x, 15, 'rgba(0, 0, 0, 0.3)', 'rgba(0, 0, 0, 0)');
ctx.fillStyle = '#fff'; // 文字颜色
ctx.setTextAlign('center');
ctx.font = 'normal 400 13px sans-serif';
ctx.fillText(`超过${this.data.canvasInfoData.participantsCount}人参与`, 140 * x, 76 * x);
ctx.restore();
ctx.draw();
that.setData({
canvasType: true
});
setTimeout(() => {
this.savePoster()
}, 300)
wx.hideLoading();
},
保存海报到相册
// 授权 getSetting检测用户有没有开启相册权限 有的话直接保存 没有的话弹到权限页面让用户授权
savePoster() {
let that = this;
wx.showLoading({
title: '正在保存',
mask: true,
});
wx.getSetting({
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
that.saveImg()
} else if (res.authSetting['scope.writePhotosAlbum'] === undefined) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
that.saveImg()
},
fail() {
wx.hideLoading();
that.authConfirm()
}
})
} else {
wx.hideLoading();
that.authConfirm()
}
}
})
},
// 授权拒绝后,再次授权提示弹窗
authConfirm() {
let that = this
wx.showModal({
content: '检测到您没打开保存图片权限,是否去设置打开?',
confirmText: "确认",
cancelText: "取消",
success: function (res) {
if (res.confirm) {
wx.openSetting({
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
that.saveImg();
} else {
wx.showToast({
title: '您没有授权,无法保存到相册',
icon: 'none'
})
}
}
})
} else {
wx.showToast({
title: '您没有授权,无法保存到相册',
icon: 'none'
})
}
}
});
},
// 图片保存到本地
saveImg() {
wx.canvasToTempFilePath({
canvasId: 'shareCanvas',
quality: 1,
success: function (res) {
wx.hideLoading();
var tempFilePath = res.tempFilePath;
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success(res) {
wx.showModal({
content: '海报保存成功,你可以从手机相册中把海报分享到朋友圈',
showCancel: false,
confirmText: '好的',
confirmColor: '#333',
})
},
fail: function (res) {
wx.showToast({
title: '保存失败',
icon: 'none',
duration: 2000
});
}
})
}
}, this);
}
问题总结
- 生成的头像显示不全
找到一篇文章解决了这个问题 感谢 canvas实现图片拉伸、压缩与裁剪 - 文字的描绘位置 textBaseline 的介绍
- 绘制圆角矩形 传送带
- canvas的
drawImage
方法只支持本地图片,不支持网络图片 base64保存本地地址 - 绘制顺序影响显示,合理使用
save()
和restore()
- 在page页面中,this是默认的,但是在自定义组件中则需要指定this
- 保存图片的时候必须打开调试才能保存,不打开就保存不了是因为需要配置
https
的域名
后端返回的如果是域名下的http
图片就需要去处理变成https