一、分享功能核心思路
1.在wxml页面中添加最重要的两个标签:image 和 canvas, image标签用于展示分享的图片,canvas用于画图、生成路径赋值给image的src,最后保存分享的图片
二、分享功能实现路线
1.UI图如下
这张设计图比较简单,数据都是从后端返回,直接渲染上去就行。主要注意标题超长时的特殊处理。此外,二维码是后端返回,然后渲染上去。实际工作中后端返回base64编码,需要编译后利用ctx.drarImage画上去
2.wxml和wxss布局
(1) 在移动端呈现的效果如下,因为做的是一个简单的demo,没有后端返回数据,所以没有做二维码。布局和样式做得比较简单。
1.1 canvas.wxml
保存图片
1.2 canvas.wxss
page {height: 100%}
.model-wrap {
position: relative;
overflow: hidden;
height: 100%;
background: rgba(0,0,0,.4);
}
.model-box {
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
image {
width: 750rpx;
height: 788rpx;
}
canvas {
position: absolute;
top: 0;
left: 750px;
width: 750px;
height: 788px;
}
.save-btn {
text-align: center;
color: #fff;
position: absolute;
top: 90%;
left: 43%;
transform: translate(0, -50%);
}
3. 开始画图
3.1 首先设置 imgUrl 并模拟一些数据
data: {
imgUrl: '', // 用于获取canvas图片路径并渲染在image标签中
shareDate: {
name: 'Damon',
title: '标题知识产权专项资金知识产权专项资金项资金项资金 ',
status: '有效',
area: '四川省成都市'
}
},
3.2 开始画图
背景图的路径需要是 https 的
const bgUrl = "https://cdn.chacha.top/mini_pro/share_bg.png";
draw() {
let _that = this; // 先保存this,如果你以组件的方式写分享功能,在生成图片时要传入this
wx.getImageInfo({
src: bgUrl,
success(res) {
let {shareDate} = _that.data;
let titleHeight; // 标题高度
let ctx = wx.createCanvasContext('myCanvas');
ctx.drawImage(res.path, 0, 0, 750, 788);
// 这里放 画在画布的文字
ctx.draw(true, (res)=> {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success(res) {
_that.setData({
imgUrl: res.tempFilePath
})
}
}, _that)
})
}
})
完成以上两步,就能把背景图画出来了,如下图所示
3.3 补充用户名、标题、其他内容
// 用户名
util.wrapText({
ctx,
text:shareDate.name,
x: 120,
y: 75,
w: 170,
fontStyle: {
lineHeight: 83,
textAlign: 'left',
textBaseline: 'top',
font: 'normal 24px arial',
fillStyle: '#333'
}
})
// 标题
// 如果标题过长时截取
let title = shareDate.title.length < 80 ? shareDate.title : shareDate.title.substring(0,80)+'...'
titleHeight = util.wrapText({
ctx,
text: title,
x: 42,
y: 195,
w: 645,
fontStyle: {
lineHeight: 58,
textAlign: 'left',
textBaseline: 'top',
font: 'normal 40px arial',
fillStyle: '#535353'
}
});
let top1 = titleHeight + 120; // 地区 在y轴上的值
let top2 = top1 + 45; // 状态 在y轴上的值
// 地区
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText('适用于 ', 42, top1);
let w1 = ctx.measureText('适用于 ').width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#ff7010';
ctx.fillText(shareDate.area, 42+w1, top1);
let w2 = ctx.measureText(shareDate.area).width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText(' 的机构', 42+w1+w2, top1);
// 政策状态
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText('政策状态:', 42, top2);
let w3 = ctx.measureText('政策状态:').width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#ff7010';
ctx.fillText(shareDate.status, 42+w3, top2);
// 长按小程序码查看详情
ctx.font = 'normal normal 24px arial';
ctx.fillStyle = '#535353';
ctx.fillText('长按小程序码查看详情', 35, 670);
呈现的效果如下,由于 标题的高度变化 会影响到适用地区和政策状态的渲染高度,所以动态设置。
3.4 用户头像
用户头像放在最后是因为 头像链接 是外链,通过异步加载获取,所以放在最后画方便控制,ctx.draw()也需要放在成功的回调中,不然会渲染不上
// 头像
wx.downloadFile({
url: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1543030619359&di=d26dfbfe8898517f3fc0d14dd7fb1437&imgtype=0&src=http%3A%2F%2Fb.hiphotos.baidu.com%2Fzhidao%2Fwh%253D450%252C600%2Fsign%3Df6277da51a4c510fae91ea1e5569091b%2F4b90f603738da97748712546b051f8198618e305.jpg',
success(res) {
let width = 63; //头像宽
let height = 63; //头像高
let x = 40; // 头像距x轴距离
let y = 55; // 头像距y轴距离
ctx.beginPath();
ctx.arc(width/2+x, height/2+y, width/2, 0, Math.PI*2);
ctx.clip();
ctx.drawImage(res.tempFilePath, x, y, width, height);
ctx.draw(true, (res)=> {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success(res) {
_that.setData({
imgUrl: res.tempFilePath
})
}
}, _that)
})
}
})
3.5 保存图片
保存图片功能比较简单,主要下载canvas图片生成的路径,在下载时调用wx.saveImageToPhotosAlbum({})这个api
save() {
let _that = this;
wx.showLoading({
mask: true
})
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 750,
height: 788,
destWidth: 750,
destHeight: 788,
canvasId: 'myCanvas',
success(res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
wx.hideLoading();
wx.showModal({
title: '提示',
showCancel: false,
confirmText: '知道了',
confirmColor: '#0facf3',
content: '已成功为您保存图片到手机相册,请自行前往朋友圈分享。',
success: (res) => {
if (res.confirm) {
console.log('保存成功,隐藏模态框')
}
}
})
},
fail(res) {
wx.hideLoading();
wx.showModal({
title: '保存出错',
showCancel: false,
confirmText: '知道了',
confirmColor: '#0facf3',
content: '您拒绝了授权 ,如果您要保存图片,请删除小程序,再重新打开。',
success: (res) => {
console.log(res)
}
})
}
})
}
}, _that)
}
4.用到的文本折行方法
const wrapText = ({
ctx,
text,
x,
y,
w,
fontStyle: {
lineHeight = 60,
textAlign = 'left',
textBaseline = 'top',
font = 'normal 40px arial',
fillStyle = '#000'
}
}) => {
ctx.save();
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
const chr = text.split('');
const row = [];
let temp = '';
for (let a = 0 ; a < chr.length ; a++) {
if (ctx.measureText(temp).width
三、总结
第一次实现分享功能时一头雾水,不知道从哪入手,看了很多教程,发现有两种方法实现。
第一种是 在页面布局时放两个 canvas,一个用于展示,另一个用于保存。这种方法要根据UI图去计算两个canvas的缩放比例,在画图时也需要画两次,第一次花在展示的canvas上,第二次根据第一次画距离*比例。我利用这种方法去做,发现比例不太方便控制,如果内容再多一些,要写更多的代码。
第二种就是今天分享的,在计算距离时,只需要根据UI图给出来的数字填上去就行,偶尔微调一下。保存出来的效果图也很好看。
项目地址:
https://github.com/keepi/canvasshare