内容需求:
小程序端生成一个海报,海报内容包括当前授权账号的头像,当前授权账号的邀请码,一张特定图片,分享页面的带对应参数的小程序二维码;生成的海报需要保存到本地
功能方法:
1.前端用canvas绘图转为图片
2.传相对应的参数 到后端 ,后端用canvas绘图转为图片传给前端
目前我们的项目是多端小程序,百度小程序和微信小程序,因为两端在实际运行的差异所以微信端使用的是前端生成,百度端使用的是后端生成的方式。
具体流程:
在生成海报之前需要根据海报的内容准备对应的图片和文字内容,这样能更稳定快速的生成海报。
!!注意如果绘图过程中使用的图片是网络图片(非本地图片)需要使用uni.getImageInfo方法对图片进行处理!!
!!
由于在获取临时路劲保存图片的时候用一倍的canvas保存的图片会很模糊,我们需要对canvas画布进行多倍处理,一般二倍即可,太大了在Android上可能会出现问题 ,我这里使用的是固定倍数3倍
也可以使用设备的像素比为倍率
const res = uni.getSystemInfoSync();
let pixelRatio = res.pixelRatio;
console.log("该设备的像素比",pixelRatio )
!!
!!在开发过程中还有遇到点小麻烦的就是如何生成对应页面并带对应参数的动态二维码呢?
解决方式是由后端请求微信服务接口 通过配置对应的参数就可以生成一个二维码啦,再将二维码发送给前端就可以了。
涉及的文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html(这里有好几种生成二维码的方法,可以根据自己的业务来调用)!!
<template>
<canvas :style="{height: pupopHeight + 'px',width: pupopWidth + 'px'}" canvas-id="myCanvas"></canvas>
<uni-popup ref="popup" type="center">
<view class="popup-wrap">
<view class="popup-head">
生成海报
<view @click="close_popup()" class="close_icon"></view>
</view>
<image :src="posterImg" mode=""></image>
<view class="popup-footer">
<button class="save-btn" @click="saveToLocal()" type="default">保存到相册</button>
<view class="tips">保存图片到相册,你就可以分享啦!</view>
</view>
</view>
</uni-popup>
</template>
<script>
data(){
return{
pupopWidth:590,
pupopHeight:788,
//一定要注意像素密度的问题,这里使用的固定数值
pixelRatio:3,//屏幕像数密度
inviteQR:'',//动态二维码
posterImg:',//最后生成的海报
}
},
mounted(){
this.pupopWidth = this.pupopWidth*this.pixelRatio
this.pupopHeight = this.pupopHeight*this.pixelRatio
console.log("屏幕像素密度",this.pixelRatio,this.pupopWidth,this.pupopHeight)
},
methods:{
//获取当前页面的二维码
//这里的做法是传对应的参数到后端,后端生成二位的图片返给前端
shareing(){
uni.showLoading({
title:"海报生成中...",
mask:true
})
//#ifdef MP-WEIXIN
//这里参数是前端和后端商议好,生成二维码需要前端传那些对应的值,这里我传了当前页面的路径和邀请码及当前页面的参数
let params={
page:this.path,
scene:this.scene
}
Api.apiGetCode(params).then(res=>{//去后端请求动态二维码
if(res.code === 2000){
this.inviteQR = res.data.qrCodePath
this.createPoster();
}
})
//#endif
},
//生成海报--微信端
createPoster(){
let _this = this
_this.headImg = uni.getStorageSync('avatarUrl')//去本地缓存获取头像
uni.getImageInfo({
src:_this.headImg,
success(image) {
const canvasId = "myCanvas"
let ctx = uni.createCanvasContext(canvasId,_this) // 自定义组件中 一定要传this ,这里一开始没加,困惑很久,一定要写一下
// 填充背景
ctx.setFillStyle('#FFFFFF')
ctx.fillRect(0,0,_this.pupopWidth,_this.pupopHeight);
ctx.save()
// 头像和二维码大小都需要在规定大小的基础上放大像素比的比例后面都会*this.pixelRatio
let headerW = 48 * _this.pixelRatio
let headerX = 40 * _this.pixelRatio
let headerY = 40 * _this.pixelRatio
// 控制头像为圆形
ctx.setStrokeStyle('rgba(0,0,0,.2)') //设置线条颜色,如果不设置默认是黑色,头像四周会出现黑边框
ctx.arc(headerX + headerW / 2, headerY + headerW / 2, headerW / 2, 0, 2 * Math.PI)
ctx.stroke()
//画完之后执行clip()方法,否则不会出现圆形效果
ctx.clip()
// 将头像画到画布上
ctx.drawImage(image.path, headerX, headerY, headerW, headerW)
ctx.restore()
ctx.save()
//绘制邀请码
const uniqueCode = uni.getStorageSync('uniqueCode') || '';
// console.log("uniqueCodeuniqueCode",uniqueCode)
let invateCode = `邀请码:${uniqueCode}`
let invateCodeX = headerX + headerW + 14 * _this.pixelRatio
let invateCodeY = headerY + headerW/2 + ((26*_this.pixelRatio)/2)
ctx.setFontSize(26*_this.pixelRatio);
ctx.setFillStyle('#333333');
ctx.fillText(invateCode, invateCodeX, invateCodeY);
ctx.stroke();
//生成banner图
uni.getImageInfo({
src: _this.banner,//这里的banner是展示的商品图
success(image) {
let bannerW = 510 * _this.pixelRatio
let bannerH = 255 * _this.pixelRatio
let bannerX = 40 * _this.pixelRatio
let bannerY = 40 * _this.pixelRatio + headerW + 24* _this.pixelRatio
// 将banner到画布上
ctx.drawImage(image.path, bannerX, bannerY, bannerW, bannerH)
ctx.restore()
ctx.save()
//banner 描述
//这里会处理多行显示文字,超出显示省略号的效果
let bannerTextX = 40 * _this.pixelRatio
let bannerTextY = bannerY + bannerH + 20 * _this.pixelRatio + 50*_this.pixelRatio //这里的y轴起始值是顶上的距离还要特意加上文字的行高
let chr = _this.bannerText.split("");//这个方法是将一个字符串分割成字符串数组
let temp = "";
let row = [];
ctx.setFontSize(30*_this.pixelRatio)
ctx.setFillStyle("#333333")
for (var a = 0; a < chr.length; a++) {
if (ctx.measureText(temp).width < 510 * _this.pixelRatio) {
temp += chr[a];
}else {
a--; //这里添加了a-- 是为了防止字符丢失,效果图中有对比
row.push(temp);
temp = "";
}
}
row.push(temp);
if (row.length > 2) {
let rowCut = row.slice(0, 2);
let rowPart = rowCut[1];
let test = "";
let empty = [];
for (var a = 0; a < rowPart.length; a++) {
if (ctx.measureText(test).width < 510 * _this.pixelRatio) {
test += rowPart[a];
}else {
break;
}
}
empty.push(test);
var group = empty[0] + "..."//这里只显示两行,超出的用...表示
rowCut.splice(1, 1, group);
row = rowCut;
}
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b], bannerTextX, bannerTextY + b * 50*_this.pixelRatio, 510 * _this.pixelRatio);
}
//画间隔线
ctx.moveTo (40 * _this.pixelRatio, bannerTextY + 40 * _this.pixelRatio + 50*_this.pixelRatio);
//设置起点状态
ctx.lineTo (_this.pupopWidth - 40 * _this.pixelRatio,bannerTextY + 40 * _this.pixelRatio + 50*_this.pixelRatio);
//设置末端状态
ctx.lineWidth = 1 * _this.pixelRatio;//设置线宽状态
ctx.strokeStyle = '#EEEEEE' ; //设置线的颜色状态
ctx.stroke();
//二维码
uni.getImageInfo({
src: _this.inviteQR,
success(res){
// 画当前页面的二维码
const img_x = 40 *_this.pixelRatio
const img_w = 160*_this.pixelRatio
const img_y = _this.pupopHeight - img_w - 40*_this.pixelRatio
ctx.drawImage(res.path,img_x,img_y,img_w,img_w)
//画提示文字
const tiptextX = img_x + img_w + 30 * _this.pixelRatio
const tiptext1Y = img_y + (img_w/2)
const tiptext2Y = img_y + (img_w/2) + 30 * _this.pixelRatio + 14 * _this.pixelRatio
const tiptext1 = '移居宝'
const tiptext2 = '长按识别二维码(微信)'
ctx.setFontSize(30*_this.pixelRatio)
ctx.setFillStyle("#333333")
ctx.fillText(tiptext1, tiptextX, tiptext1Y);
ctx.stroke();
ctx.setFontSize(26*_this.pixelRatio)
ctx.setFillStyle("#666666")
ctx.fillText(tiptext2, tiptextX, tiptext2Y);
ctx.stroke();
ctx.draw(false, () => {
uni.canvasToTempFilePath({
width: _this.pupopWidth,
height: _this.pupopHeight,
destWidth: _this.pupopWidth,
destHeight:_this.pupopHeight,
canvasId: canvasId,
fileType: 'png',
quality:1,
success: function(res) {
_this.posterImg = res.tempFilePath;//最终将canvas转换为图片
_this.$refs.popup.open();
uni.hideLoading()
},
fail(error) {
console.log('4',error)
toast({
msg:'生成海报失败,请稍后重试!'
})
setTimeout(()=>{
uni.hideLoading()
},2000)
}
},_this)
})
},
fail(error) {
console.log('获取二维码失败',error)
toast({
msg:'生成海报失败,获取二维码失败'
})
setTimeout(()=>{
uni.hideLoading()
},2000)
}
})
},
fail(error) {
console.log('生成商品图失败',error)
toast({
msg:'生成海报失败,获取商品图失败'
})
setTimeout(()=>{
uni.hideLoading()
},2000)
}
});
},
fail(error) {
console.log('生成头像失败',error)
toast({
msg:'生成海报失败,获取头像失败'
})
setTimeout(()=>{
uni.hideLoading()
},2000)
}
})
},
//将图片保存到本地相册
saveToLocal(){
//#ifdef MP-WEIXIN
uni.saveImageToPhotosAlbum({
filePath: this.posterImg,
success: ()=>{
toast({
msg:'保存到相册成功'
})
this.$refs.popup.close()
},
fail:(err)=>{
console.log("保存到相册失败",err)
}
});
//#endif
}
}
</script>
总结:
涉及的知识点: