小程序生成分享海报,带小程序码
话不多说,先上效果图
效果图不是重点,海报基本都是大同小异,都是一些信息,加上一个小程序码,重点是如何将所有要素整合成跟效果图一样的图片。我们知道如果是H5的话,有html2canvas可以使用,非常方便,感兴趣的同志可以参考我的另一篇H5生成分享海报
但是,小程序没有提供类似的东西啊,怎么办呢,只能自己动手画了,这就用到了canvas了。
先上我自己项目里的代码吧,结合上图:
<!-- 画布canvas -->
<canvas style="width: 343px;height: {{canvasHeight+'px'}};position:fixed;top:-9999rpx;left:-9999rpx;z-index:999" canvas-id="mycanvas"/>
<!-- 分享海报弹出层 -->
<van-popup show="{{ true }}" position="center" safe-area-inset-top="{{true}}" overlay="{{false}}" bind:close="onClose" round>
<view class="share_bill" style="height:{{innerViewH}}">
<view class="inner_view">
<view class="top_name">
<image src="{{activityDetail.activity_imgs[0]}}?x-oss-process=image/resize,m_fill,h_302,w_686"></image>
<view class="bg_view"></view>
<view class="title">
<image src="{{memberDetail.headimgurl?(memberDetail.headimgurl+'?x-oss-process=image/resize,m_fill,h_96,w_96'):'../../images/avatar.png'}}"></image>
<text>{{nickname}}</text>
</view>
</view>
<view class="bottom_msg">
<view class="msg_info">
<view class="title">
<text class="pub_tip">{{activityDetail.is_public==1?'公开':'非公开'}}</text>
{{activityDetail.activity_name}}
</view>
<view class="msg_list">
<image class="msg_icon" src="../../images/hd_adress@2x.png"></image>
<text class="msg_text">{{activityDetail.wx_address}} | {{activityDetail.distance}}</text>
</view>
<view class="msg_list">
<image class="msg_icon" src="../../images/hd_time@2x.png"></image>
<text class="msg_text">{{activityDetail.time}}</text>
</view>
<view class="msg_list">
<image class="msg_icon" src="../../images/hd_enroll@2x.png"></image>
<text class="msg_text">总人数 {{activityDetail.can_sign_num}}</text>
<view class="fu_price">
<text class="fu" wx:if="{{activityDetail.fee_type==1}}">¥</text>
<text class="price">{{activityDetail.fee_type==0?'免费':(activityDetail.fee_type==2?'AA收款':(activityDetail.man_fee>activityDetail.woman_fee?activityDetail.woman_fee:activityDetail.man_fee))}}</text>
</view>
</view>
</view>
<view class="logo_code">
<view class="logo_box">
<image src="../../images/share_logo.png"></image>
<view class="logo_msg">
<text>羽乐papa报名</text>
<view>长按识别二维码参与</view>
</view>
</view>
<view class="code_box">
<image src="{{codeImg}}"></image>
</view>
</view>
</view>
</view>
</view>
</van-popup>
waitCreate(){
// 生成海报
var that = this
wx.showLoading({
title: '海报生成中···',
mask:'true'
})
setTimeout(() => {
var obj=wx.createSelectorQuery();
obj.selectAll('.inner_view').boundingClientRect();
obj.exec(function (rect) {
that.setData({
canvasHeight:rect[0][0].height
})
that.createNewImg()
})
}, 1500);
},
// 生成海报方法
createNewImg: function () {
var that = this;
//绘制海报底板,宽343,高海报的高度that.data.canvasHeight,圆角8;具体尺寸可根据实际设计图自己修改
var context = wx.createCanvasContext('mycanvas');
context.arc(8, 8, 8, Math.PI, Math.PI*1.5)
context.arc(343 - 8, 8, 8, Math.PI * 1.5, Math.PI * 2)
context.arc(343-8, that.data.canvasHeight - 8, 8, 0, Math.PI * 0.5)
context.arc(8, that.data.canvasHeight - 8, 8, Math.PI * 0.5, Math.PI)
context.clip()
context.strokeStyle = "#ffffff";
context.fillStyle="#ffffff"
context.fill()
// context.stroke();
context.drawImage(that.data.activityCanvasPath, 0, 0, 343, 151);//绘制活动图片
context.save()
// 绘制昵称蒙层
const grd = context.createLinearGradient(0, 0, 0, 343)
grd.addColorStop(0, 'rgba(0, 0, 0, 0)')
grd.addColorStop(1, 'rgba(0, 0, 0, 0.3)')
context.setFillStyle(grd)
context.fillRect(0,103,343,48);
// 绘制昵称
context.setFontSize(15);
context.setFillStyle('#ffffff');
context.setTextAlign('left');
context.fillText(utils.StringExchangeEmoji(that.data.memberDetail.nickname),60,113+15)
context.restore()
// 绘制公开非公开
context.setFontSize(13);
context.setFillStyle('#ffffff');
context.setTextAlign('left');
var pub_w = 0;
if(that.data.activityDetail.is_public==1){
pub_w = context.measureText('公开').width+8
}else{
pub_w = context.measureText('非公开').width+8
}
console.log(pub_w)
context.save()
context.beginPath();
const pub_bg = context.createLinearGradient(0, 0, pub_w,0)
pub_bg.addColorStop(0, 'rgba(0, 84, 220, 0.4)')
pub_bg.addColorStop(1, 'rgba(0, 209, 162, 0.4)')
context.arc(16+2, 170+2, 2, Math.PI, Math.PI*1.5)
context.arc(16+pub_w - 2, 170+2, 2, Math.PI * 1.5, Math.PI * 2)
context.arc(16+pub_w-2, 170+18 - 2, 2, 0, Math.PI * 0.5)
context.arc(16+2, 170+18 - 2, 2, Math.PI * 0.5, Math.PI)
context.setFillStyle(pub_bg)
context.fill()
context.closePath();
context.restore()
context.fillText(that.data.activityDetail.is_public==1?'公开':'非公开',20,170+13);
// 绘制活动名称
context.setFontSize(17);
context.setFillStyle('#333333');
context.setTextAlign('left');
let s = "我是a_dddhuo互动名称11111111受到冲击难道就省的111吨吨吨吨"
let str = that.data.activityDetail.activity_name.split("")
var temp = '';
var row = [];
for(var i = 0;i<str.length;i++){
console.log(row,'row???')
console.log(context.measureText(temp),'measureTextwidth???')
if(row.length<1){
if(that.data.activityDetail.is_public==1){
if(context.measureText(temp).width < 269){
}else{
row.push(temp);
temp = "";
}
}else{
if(context.measureText(temp).width < 256){
}else{
row.push(temp);
temp = "";
}
}
}else{
if(context.measureText(temp).width < 311){
}else{
row.push(temp);
temp = "";
}
}
temp += str[i];
}
row.push(temp)
console.log(row,'row')
for(var b = 0; b < row.length; b++){
if(b==0){
context.fillText(row[b],that.data.activityDetail.is_public==1?58:71,167+(b+1)*17);
context.fillText(row[b],that.data.activityDetail.is_public==1?(58+0.5):(71+0.5),167+(b+1)*17+0.5);
}else{
context.fillText(row[b],16,167+(b+1)*17);
context.fillText(row[b],16+0.5,167+(b+1)*17+0.5);
}
}
context.save()
// 绘制地址人数时间信息
context.drawImage('/images/hd_adress@2x.png', 16, 201+row.length*17-17, 14, 14);
context.setFontSize(13);
context.setFillStyle('#999999');
context.setTextAlign('left');
let name_str = that.data.activityDetail.wx_address.split("")
var name_temp = '';
var name_row = [];
for(var i = 0;i<name_str.length;i++){
if(context.measureText(name_temp).width < 277){
}else{
name_row.push(name_temp);
name_temp = "";
}
name_temp += name_str[i];
}
name_row.push(name_temp)
for(var b = 0; b < name_row.length; b++){
context.fillText(name_row[b], 34, 199+(b+1)*13+row.length*17-17);
}
context.drawImage('/images/hd_time@2x.png', 16, 223+(name_row.length*13)-13+row.length*17-17, 14, 14);
context.fillText(that.data.activityDetail.time, 34, 221+(name_row.length*13)+row.length*17-17);
context.drawImage('/images/hd_enroll@2x.png', 16, 245+(name_row.length*13)-13+row.length*17-17, 14, 14);
context.fillText('总人数'+that.data.activityDetail.can_sign_num, 34, 243+(name_row.length*13)+row.length*17-17);
// 价格
context.restore()
context.setFontSize(15);
context.setFillStyle('#F13138');
context.setTextAlign('left');
if(that.data.activityDetail.fee_type==0){
let w = context.measureText('免费').width
context.fillText('免费', 343-16-w, 237+15+(name_row.length*13)-10+row.length*17-17);
}
if(that.data.activityDetail.fee_type==2){
let w = context.measureText('AA收款').width
context.fillText('AA收款', 343-16-w, 237+15+(name_row.length*13)-10+row.length*17-17);
}
if(that.data.activityDetail.fee_type==1){
if(that.data.activityDetail.man_fee>that.data.activityDetail.woman_fee){
let w = context.measureText('¥'+that.data.activityDetail.woman_fee).width
context.fillText('¥'+that.data.activityDetail.woman_fee, 343-16-w, 237+15+(name_row.length*13)-10+row.length*17-17);
}else{
let w = context.measureText('¥'+that.data.activityDetail.man_fee).width
context.fillText('¥'+that.data.activityDetail.man_fee, 343-16-w, 237+15+(name_row.length*13)-10+row.length*17-17);
}
}
// context.fillText('¥'+that.data.activityDetail.man_fee, 297, 278+13+row.length*17-17);
// 画线
context.save()
context.moveTo(16,277+(name_row.length*13)-13+row.length*17-17)
context.lineTo(327,277+(name_row.length*13)-13+row.length*17-17)
context.strokeStyle="#E5E5E5"
context.stroke()
// 绘制logo
context.restore()
context.drawImage('/images/share_logo.png', 16, 319+(name_row.length*13)-13+row.length*17-17-30, 48, 48);
// 绘制小程序码
context.drawImage(that.data.codeCanvasPath, 227, 293+(name_row.length*13)-13+row.length*17-17-15, 100, 100);
context.setFontSize(15);
context.setFillStyle('#333333');
context.setTextAlign('left');
context.fillText("羽乐papa报名", 76, 319+15+(name_row.length*13)-13+row.length*17-17-30);
context.setFontSize(13);
context.setFillStyle('#999999');
context.setTextAlign('left');
context.fillText("长按识别二维码参与", 76, 346+19+(name_row.length*13)-13+row.length*17-17-30);
context.save()
// 画头像
context.beginPath();
context.arc(32, 123, 16, 0, 2 * Math.PI) //画出圆
context.strokeStyle = "#ffffff";
context.fill()
context.clip(); //裁剪上面的圆形
context.drawImage(that.data.avatarCanvasPath, 16, 107, 32, 32); // 在刚刚裁剪的园上画图
context.restore();
context.closePath();
context.draw();
setTimeout(function () {
wx.hideLoading()
wx.canvasToTempFilePath({//画出的海报转成图片链接,页面展示用,或保存图片时用
canvasId: 'mycanvas',
success: function (res) {
var tempFilePath = res.tempFilePath;
console.log("海报的图片链接",tempFilePath)
that.setData({
imagePath:tempFilePath
})
},
fail: function (res) {
console.log(res);
}
});
}, 1000);
},
逐步解析:
1,首先是画海报的整个底板,也就是接下来的画板区域,它决定了你的有效绘画区域,就跟你拿张纸画画一样,你能画的地方就只限于这张纸上。context.arc(x,y,r,sAngle,eAngle,counterclockwise) 方法创建弧/曲线(用于创建圆或部分圆)。x,y圆心坐标,r圆半径,sAngle起始角,eAngle结束角,counterclockwise,可选,false顺时针,true逆时针。
2,context.clip(),剪切;
3,context.strokeStyle,笔触颜色,就是啥颜色的笔画;
4,context.fillStyle,填充色,可以理解为背景颜色;
5,context.fill(),填充当前的图像(路径);
6,context.drawImage(img,x,y,width,height),将现有图片绘制到canvas上,img图片地址,x,y坐标,width,height宽高大小;
7,context.save(),把当前状态的一份拷贝压入到一个保存图像状态的栈中。这就允许您临时地改变图像状态,然后,通过调用 restore() 来恢复以前的值。与context.restore()成对出现;
8,context.createLinearGradient(x0,y0,x1,y1),创建线性的渐变对象。渐变可用于填充矩形、圆形、线条、文本等等。x0渐变开始点的 x 坐标,y0渐变开始点的 y 坐标,x1渐变结束点的 x 坐标,y1渐变结束点的 y 坐标;
9,grd.addColorStop(),渐变对象中的颜色和位置;
10,context.setFillStyle,设置或返回用于填充绘画的颜色、渐变或模式;
11.context.fillRect(x,y,width,height),绘制"已填充"的矩形,x,y矩形左上角的坐标;
12,context.setFontSize(),设置字体大小;
13,context.setTextAlign(),设置字体对齐方式;
14,context.fillText(text,x,y,maxWidth),在画布上绘制填色的文本,text要绘制的文字,x,y起始坐标,相对于画布,maxWidth可选,允许的最大文本宽度,以像素计;
15,context.measureText(text).width,返回字体的宽度,text要测量的字体,可用于判断字体太多,进行换行绘制;
16,context.beginPath(),开始一条路径,或重置当前的路径;
17,context.closePath(),创建从当前点到开始点的路径;
18,context.moveTo(x,y),把路径移动到画布中的指定点,不创建线条,路径目标位置坐标;
19,context.clip(),从原始画布中剪切任意形状和尺寸,一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。您也可以在使用 clip() 方法前通过使用 save() 方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过 restore() 方法);
20,context.draw(),整体绘制。
绘制完成后,wx.canvasToTempFilePath(),微信小程序的这个方法,可以生成canvas图片链接。接下来就是保存图片,同样微信小程序也提供了对应的方法,
// 点击保存图片
saveImg(){
var that = this
// console.log(mycanvas,'mycanvas')
// 保存图片
wx.canvasToTempFilePath({
canvasId: 'mycanvas',
success: function (res) {
var tempFilePath = res.tempFilePath;
console.log("海报的链接",tempFilePath)
that.setData({
imagePath:tempFilePath
})
wx.saveFile({
tempFilePath: tempFilePath,
success (res) {
console.log("resresrese",res)
const savedFilePath = res.savedFilePath
wx.saveImageToPhotosAlbum({
filePath: savedFilePath,
success: function (res) {
wx.showToast({
title: '已保存至手机相册',
icon:'none',
duration:2000
})
},
fail: function (res) {
wx.showToast({
title: '请在左上角设置中开启保存相册权限!',
icon:'none',
duration:5000
})
},
})
}
})
},
fail: function (res) {
console.log(res);
}
});
},
备注:值得一提的是,小程序在context.drawImage()绘制图片的时候,是不支持网络路径的图片的,支、只支持本地路径的图片!!!!解决办法,将线上路径的图片转化为本地路径的图片:
this.setImgPath(res.data.param.url).then(function(data){
that.setData({
codeCanvasPath:data
})
})
// 链接图片转画图路径
setImgPath(path){//传入图片线上路径
return new Promise((resolve,reject)=>{
wx.getImageInfo({
src:path,
success (res) {
resolve(res.path)
}
});
})
},