一、微信小程序端使用canvas渲染商品海报
1、canvas合成主函数。
//合成海报(商品)
getProductPosterTest() {
var that = this
const iamgeRate = that.data.iamgeRate //图片宽高比
wx.createSelectorQuery()
.select('#myCanvas') // 在 WXML 中填入的 id
.fields({
node: true,
size: true
})
.exec((res) => {
// Canvas 对象
const canvas = res[0].node
// 渲染上下文
const ctx = canvas.getContext('2d')
// Canvas 画布的实际绘制宽高
const width = res[0].width
const height = res[0].height
console.log('width', width);
console.log('height', height);
// 初始化画布大小
console.log('dpr', wx.getWindowInfo());
const dpr = wx.getWindowInfo().pixelRatio
canvas.width = width * dpr
canvas.height = height * dpr
ctx.scale(dpr, dpr)
// 设置背景为白色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 使用Promise 写成链式调用,顺序渲染图片
that.canvasCover(canvas, ctx, iamgeRate, width).then(cov => {
return that.canvasAvatar(canvas, ctx, iamgeRate, width)
}).then(res => {
return that.canvasCode(canvas, ctx, iamgeRate, width)
})
//不加定时器容易出现空白图
setTimeout(() => {
wx.canvasToTempFilePath({
canvas: canvas,
success: res => {
// 生成的图片临时文件路径
that.setData({
img: res.tempFilePath
})
},
})
}, 500)
})
},
2、设置海报背景图渲染,同时渲染海报文字内容。商品名称只显示两行,超出的用'...'替代
// 设置封面图
canvasCover(canvas, ctx, iamgeRate, width) {
let that = this;
return new Promise(cov => {
//商品信息文案
let str = that.data.productInfo.productName
if (str.length > 15) {
str = str.slice(0, 15) + '...'
}
ctx.font = "16px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#000000";
ctx.fillText(str, 10, width / iamgeRate + 30, width - 20);
ctx.font = "20px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#FF0202";
ctx.fillText('¥'+that.data.productInfo.productPrice, 10, width / iamgeRate + 60, width - 20);
//分享人信息文案
ctx.font = "15px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#000000";
ctx.fillText(wx.getStorageSync('memberInfo').memberNickname, 80, width / iamgeRate + 100);
ctx.font = "14px Arial";
ctx.fillStyle = "#939393";
ctx.fillText('邀请您来XXXX', 80, width / iamgeRate + 130);
const image = canvas.createImage()
image.src = that.data.localBgImg
image.onload = () => {
ctx.drawImage(image, 0, 0, width, width / iamgeRate)
}
cov(true)
})
},
3、渲染头像,渲染时,将头像裁成圆形
// 设置 头像 设置头像 (裁剪圆形)
canvasAvatar(canvas, ctx, iamgeRate, width) {
var that = this
return new Promise(res => {
const logo = canvas.createImage()
logo.src = that.data.localLogoImg
logo.onload = () => {
//头像裁圆
ctx.save()
ctx.beginPath()
ctx.arc(40, width / iamgeRate + 80 + 30, 30, 0, 2 * Math.PI)
// ctx.fill()
ctx.clip()
ctx.drawImage(logo, 10, width / iamgeRate + 80, 60, 60)
ctx.restore()
console.log('头像完成')
res(true)
}
})
},
4、最后将小程序码渲染在画布上
// 设置小程序码
canvasCode(canvas, ctx, iamgeRate, width) {
let that = this
return new Promise(cod => {
const imgs = canvas.createImage()
imgs.src = that.data.localQrcodeImg //二维码图片
imgs.onload = () => {
ctx.drawImage(imgs, width - 110, width / iamgeRate + 55, 100, 100)
}
})
},
5、效果
二、uniapp 使用canvas 渲染合成图片
1、uniapp 小程序端合成证书
设置canvas标签
<canvas id="myCanvas" canvas-id="myCanvas" style="width: 391px;height: 496px;" ></canvas>
let ctx = wx.createContext('myCanvas', this),
渲染canvas
//渲染canvas
drawCertificate(){
var that = this;
that.imgurl = that.renderData.url
that.getSystemInfo(); //获取设备信息
wx.getImageInfo({
src: that.imgurl,
success: function(ress) {
that.ctx.drawImage(ress.path, 0, 0, that.w, that.h, 0, 0, 391, 496);
that.ctx.font = '8px sen-serif'
that.ctx.fillStyle = "#000"
that.ctx.fillText('证书编号:' + that.renderData.recordCode, 50, 50)
that.ctx.font = '25px sen-serif'
var text = that.renderData.certificateName;
var textWidth = that.ctx.measureText(text).width;
var canvasWidth = that.w;
var xPosition = canvasWidth / 2 - textWidth / 2;
that.ctx.fillText(text, xPosition, 75);
that.ctx.font = '11px sen-serif'
let text1 = that.renderData.certificateText
text1 = text1.replace(/#company#/g, that.renderData.companyName)
text1 = text1.replace(/#name#/g,' ' + that.renderData.name)
let level = that.renderData.levelName.substring(0,that.renderData.levelName.lastIndexOf('考试'))
text1 = text1.replace(/#level#/g, ' ' + level + ' ')
text1 = " " + text1
//超长文版按行渲染
that.fillTextWrapSelf(this.ctx, {
text: text1,
x: 40,
y: 112,
size: 11,
color: '#000000',
indent: 1,
lineHeight: 28,
maxWidth: 305,
maxHeight: 180,
vertical: 'center'
});
let text2 = " 经考核合格,能够对该机器进行装机,保养和维修工作。"
//超长文版按行渲染
that.fillTextWrapSelf(this.ctx, {
text: text2,
x: 55,
y: 200,
size: 11,
color: '#000000',
indent: 1,
lineHeight: 28,
maxWidth: 305,
maxHeight: 180,
vertical: 'center'
});
that.ctx.fillText(`特发此证`, 300, 300)
that.ctx.fillText(`培训讲师:`, 240, 350)
that.ctx.fillText(`部门负责人:`, 240, 380)
//名称下横线
// that.ctx.beginPath()
// that.ctx.moveTo(219, 381)
// that.ctx.lineTo(320, 381)
// that.ctx.stroke()
// that.ctx.beginPath()
// that.ctx.moveTo(219, 422)
// that.ctx.lineTo(320, 422)
// that.ctx.stroke()
let text3 = that.renderData.traner
that.ctx.fillText(text3, 310, 348)
let text4 = that.renderData.manager
that.ctx.fillText(text4, 309, 380)
let text5 = that.renderData.issuingUnit
that.ctx.fillText(text5, 195, 428)
that.ctx.fillText(that.renderData.departmentName, 290, 455)
that.ctx.restore()
wx.drawCanvas({
canvasId: 'myCanvas',
reserve: true,
actions: that.ctx.getActions() // 获取绘图动作数组
});
//将canvas 画布内容导出图片
setTimeout(() => {
wx.canvasToTempFilePath(
{
width: that.w,
height: that.h,
canvasId: 'myCanvas',
success: function success(res) {
console.log('保存图片', res);
that.showUrl = res.tempFilePath;
console.log('showUrl',that.showUrl);
},
fail: function(res){
console.log("err", res);
},
complete: function(){
}
},
this
);
},500)
},
fail(err) {
console.log('err', err);
}
})
},
长文本逐行渲染方法
fillTextWrapSelf (ctx, obj) {
let {
text,
x, y,
size,
color,
bold,
indent,
maxWidth,
maxHeight,
lineHeight,
vertical} = obj
// 默认值
bold = bold || false
indent = indent || 0
lineHeight = lineHeight || 24
vertical = vertical || 'top'
this.ctx.save()
this.ctx.setTextAlign('left')
this.ctx.setTextBaseline('normal')
this.ctx.setFillStyle(color)
if (bold) {
this.ctx.font = 'normal bold ' + Math.round(size) + 'px sans-serif'
} else {
this.ctx.setFontSize(size)
}
let textArr = text.split('')
let rowArr = [] // 每行文本数组
let rowText = ''// 当前行文本
let rowWid = 0 // 当前行宽度
let lastText = text// 最后一行文本
for (let i = 0; i < textArr.length; i++) {
let oMaxWidth = maxWidth
if (rowArr.length === 0 && indent) {
oMaxWidth = maxWidth - indent * size
}
rowText = rowText + textArr[i]
rowWid = parseInt(this.ctx.measureText(rowText + '').width)
if (rowWid >= oMaxWidth) {
rowArr.push(rowText)
lastText = lastText.substr(rowText.length)
// 重置参数
rowText = ''
rowWid = 0
}
}
if (lastText) rowArr.push(lastText)
let rows = rowArr.length // 行数
let bY = y
if (vertical === 'center' && maxHeight > rows * lineHeight) {
bY = y + Math.ceil((maxHeight - rows * lineHeight) / 2)
}
for (let i = 0; i < rowArr.length; i++) {
let cY = bY + i * lineHeight
// if (cY + lineHeight > maxHeight + y) {
// this.ctx.fillText(rowArr[i], x, cY)
// break
// }
if (i === 0 && indent) { // 首行缩进
this.ctx.fillText(rowArr[i], x + indent * size, cY)
} else {
this.ctx.fillText(rowArr[i], x, cY)
}
}
this.ctx.restore()
}
2、uniapp app端合成图片
设置Canvas dom
<canvas id="myCanvas" canvas-id="myCanvas" type="2d" class="canvasBox"/>
初始化canvas
//初始化Canvas
drawImage() {
var that = this
const query = uni.createSelectorQuery().in(that);
query.select('#myCanvas').boundingClientRect(data => {
that.canvasHeigth = data.height
that.canvasWidth = data.width
that.context = uni.createCanvasContext('myCanvas')
console.log('coverImage', that.mainImage);
var mainImageTmp = this.checkDownloadImgUrl(that.mainImage)
that.bgImage = mainImageTmp
that.context.drawImage(mainImageTmp, 0, 0, that.canvasWidth, that
.canvasHeigth)
//画
that.context.draw()
}).exec()
},
画图
drawImage(){
this.bgImage = showImageTmp
this.context.drawImage(bgImage , 0, 0, this.canvasWidth, this.canvasHeigth)
//画
this.context.draw()
//转换图片步骤同小程序版
}
三、vue端实现canvas绘图
<canvas id="effect" style="width: 391px;height: 496px;">
您的浏览器暂不支持此功能,请升级后重试。
</canvas>
//合成证书
toViewEffect() {
let canvas = document.getElementById('effect'); //获得画布
let ctx = canvas.getContext('2d');
canvas.width = 391;
canvas.height = 496;
// 背景图片
let img = new Image();
// img.src = $('#smallImg').val();
img.src = this.info.baseCode;
img.alt = '';
img.onload = () => {
//图片加载完成后,执行此方法
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
// 编号
ctx.font = "8px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#000000";
ctx.fillText(`证书编号:${this.info.recordCode}`, 45, 45);
// 标题
ctx.font = "25px Arial";
ctx.textAlgin = "center"
ctx.fillStyle = "#000000";
// let text = document.getElementById("certificateName").value
let text = this.info.certificateName
let textWidth = ctx.measureText(text).width;
let canvasWidth = canvas.width;
let xPosition = canvasWidth / 2 - textWidth / 2;
ctx.fillText(text, xPosition, 75);
// 正文
// let member = JSON.parse(localStorage.getItem("member"))
let str = this.info.certificateText;
str = str.replace(/#company#/g, this.info.companyName)
str = str.replace(/#name#/g, ' ' + this.info.name + ' ')
let level = this.info.levelName.substring(0,this.info.levelName.lastIndexOf('考试'))
str = str.replace(/#level#/g, ' ' + level + ' ')
let mainText = ' ' + str;
ctx.font = "11px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#000000";
//截取固定长度,逐行渲染
let temp = '', row = [], maxwidth = 305
for (let a = 0; a < mainText.length; a++) { // #将文本按最大宽度换行
// #判断文本是否超出,是则换行
(ctx.measureText(temp).width >= maxwidth) && (row.push(temp), temp = "")
temp += mainText[a];
}
row.push(temp);
for (let b = 0; b < row.length; b++) { // #按行写入文本
ctx.fillText(row[b], 45, 190 + (b - row.length / 2) * 25, maxwidth); // #40是行高
}
//结论
let conclusionText = ' 经考核合格,能够对该机器进行装机,保养和维修工作。'
ctx.font = "11px Arial";
ctx.textAlgin = "left"
ctx.fillStyle = "#000000";
ctx.fillText(conclusionText, 55, 290, maxwidth);
ctx.font = "11px Arial";
ctx.textAlgin = "right"
ctx.fillStyle = "#000000";
ctx.fillText('特发此证。', 275, 310);
//培训讲师
ctx.font = "11px Arial";
ctx.textAlgin = "right"
ctx.fillStyle = "#000000";
ctx.fillText('培训讲师: ' + this.info.traner, 220, 365);
//部门负责人
ctx.font = "11px Arial";
ctx.textAlgin = "right"
ctx.fillStyle = "#000000";
ctx.fillText('部门负责人: ' + this.info.manager, 220, 395);
// 落款1
ctx.font = "11px Arial";
ctx.textAlgin = "right"
ctx.fillStyle = "#000000";
ctx.fillText(this.info.issuingUnit, 190, 435);
// 落款2
ctx.font = "11px Arial";
ctx.textAlgin = "right"
ctx.fillStyle = "#000000";
ctx.fillText(this.info.departmentName, 290, 460);
//讲师下划线
//绘制直线
ctx.moveTo(290, 370)
ctx.lineTo(350, 370)
ctx.moveTo(290, 400)
ctx.lineTo(350, 400)
ctx.lineWidth = '.4'; //线的宽度
ctx.strokeStyle = '#000000'; //线的颜色
ctx.stroke()
this.base64 = canvas.toDataURL('image/jpg');
}
},
需要注意的是,vue端要渲染的背景图片容易出现跨域并且不容易解决,能渲染在页面展示,但是导出图片报错或者空白图片,最后让后端同事将图片转成base64格式的返回给我。同时,vue端导出的图片格式也是base64格式的,需要处理。