文章目录
- canvas使用记录
- 先看下实际效果图
- 绘制流程及思路
- 1. 绘制头像, 通过`drawImage`来绘制
- 2.绘制文字部分
- 具体代码
分享海报图片的方式,以前再RN端采用的是截图方案, 我记得组件好像是
react-native-view-shot
现在要处理uni-app的海报图片分享, 一般也有
html2canvas
的相关插件
不过其缺点也有,
比如说遇到bug,有时候没办法修改什么的
手绘canvas虽然麻烦,但是胜在自由灵活
canvas使用记录
先看下实际效果图
绘制流程及思路
CanvasContext文档
其实使用下来发现, canvas绘制和iOS原生开发进行UI绘制有很多相似之处, 比如draw的入参 ,都需要x
,y
坐标,设置width/height等
整个canvas绘制的思路如下
0.绘制整个大矩形背景 : 通过uni.downloadFile
下载网络图片来获取tempFilePath
, 结合几个坐标和宽高参数, 就可以绘制了
1. 绘制头像, 通过drawImage
来绘制
不过这里需要注意的是, 如果要对头像图片进行裁剪,比如圆心之类的
需要用到clip
的情况下,
需要提前保存好上下文画布
// 2.顶部头像
ctx.save(); // 先保存之前的画布
ctx.beginPath()
ctx.arc((200 + 190 / 2) * scaleNum, (60 + 190 / 2) * scaleNum, 190 / 2 * scaleNum, 0,
Math.PI * 2)
ctx.clip()
ctx.drawImage(this.headUrl, 200 * scaleNum, 60 * scaleNum, 190 * scaleNum, 190 *
scaleNum)
(200: 左边间距)
(190: 圆的直径)
(60: top的间距)
(这里需要注意的事, 圆的圆心坐标, 是相对于x/y坐标的哦 )
2.绘制文字部分
这个没有太大的问题, 不过要注意下 textAlign
的具体用法
具体代码
- html部分
<canvas :style="{width: boxDetail.width +'px', height: boxDetail.height + 'px' }"
id="sharePic" canvas-id="sharePic"></canvas>
2.js部分
setCanvasSize() {
const query = uni.createSelectorQuery().in(this);
query.select('#content').boundingClientRect(data => {
this.boxDetail.width = data.width
this.boxDetail.height = data.height
console.log(this.boxDetail, data)
this.$nextTick(() => {
setTimeout(() => {
console.log('create and set canvas')
this.createCanvas()
// this.asyncAwaitCanvas()
}, 800)
})
}).exec();
},
/**
* @Description: 创建canvas画布
*/
async createCanvas() {
this.context = uni.createCanvasContext('sharePic', this)
const ctx = this.context;
function drawCanvas(url, top) {
if (url) {
return new Promise((resolve, reject) => {
let obj
obj = { url }
uni.downloadFile({
url,
success: (res) => {
top ? resolve({ ...res, top }) : resolve({ ...res })
},
fail: (err) => {
reject(err)
}
})
})
} else {
return new Promise((resolve, reject) => {
top ? resolve({ top }) : resolve()
})
}
}
const scaleNum = this.boxDetail.width / 590
// 1.背景图
// - 读背景图资源
let res = await drawCanvas(
"https://cdn.froglesson.com/static/cert/share_notes_content_bg.png");
ctx.save();
// - 绘制背景图路径/坐标/宽/高
ctx.drawImage(res.tempFilePath, 0, 0, scaleNum * 590, scaleNum * 864)
// 2.顶部头像
ctx.save(); // 先保存之前的画布
ctx.beginPath()
ctx.arc((200 + 190 / 2) * scaleNum, (60 + 190 / 2) * scaleNum, 190 / 2 * scaleNum, 0,
Math.PI * 2)
ctx.clip()
ctx.drawImage(this.headUrl, 200 * scaleNum, 60 * scaleNum, 190 * scaleNum, 190 *
scaleNum)
// 3.title
ctx.restore() //恢复一下绘画板
let title = this.returnName(this.shareData.user_name + ",已学习" + (this.shareData
.count || 0) +
"天")
ctx.setFontSize(32 * scaleNum) //字体大小
ctx.setFillStyle('#fff') //文字颜色
ctx.setTextAlign('center') //文本左对其
ctx.fillText(title, 590 / 2 * scaleNum, (280 + 30) * scaleNum, 530 *
scaleNum); // marginleft:182rpx;maxWidth:374 最大宽度
// 4.学习内容
let content = "今天在「XXXXAPPXXXXX」" + (this.shareData.times > 30 ?
`${this.shareData.times || 0}分钟` : "学习了")
content = content + this.studyTitle() + this.studyCount();
content = "我是中间文字部分,阿爸不不不不不不不不不,啊电话多好多好多好多好等哈,多撒MDJSLSJL"
// - 字符拆分/计算行数
let content_top = 0
let contents = content.trim().split("")
contents = contents.map((item, index) => {
if (index && !(index % 20)) {
item = item + '\n';
}
return item
})
// 逐行绘制标题文字
contents.join("").split('\n').forEach((item, index) => {
ctx.setFontSize(28 * scaleNum) //字体大小
ctx.setFillStyle('#fff') //文字颜色
ctx.setTextAlign('center') //文本左对其
content_top = (365 + 30) * scaleNum + 48 * scaleNum * index //高度计算, 40是行高
ctx.fillText(item, 590 / 2 * scaleNum, content_top, 530 *
scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度
})
// 5.时间
let time = this.shareData.day
ctx.setFontSize(24 * scaleNum) //字体大小
ctx.setFillStyle('#fff') //文字颜色
ctx.setTextAlign('center') //文本左对其
ctx.fillText(time, 590 / 2 * scaleNum, (609 + 30) * scaleNum, 530 *
scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度
// 6.icon
res = await drawCanvas("https://cdn.froglesson.com/static/cert/index_logo.png")
ctx.drawImage(res.tempFilePath, 210 * scaleNum, (716 + 30) * scaleNum, 80 * scaleNum,
80 * scaleNum)
// 7.qrcode
res = await drawCanvas(this.qrCode);
ctx.drawImage(res.tempFilePath, 299 * scaleNum, (716 + 30) * scaleNum, 80 * scaleNum,
80 * scaleNum)
// 8.底部title
let appName = "XXXXAPPNAMEXXXXXX"
ctx.setFontSize(24 * scaleNum) //字体大小
ctx.setFillStyle('#fff') //文字颜色
ctx.setTextAlign('center') //文本左对其
ctx.fillText(appName, 590 / 2 * scaleNum, (801 + 50) * scaleNum, 530 *
scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度
// 完成绘制 - 执行保存
ctx.draw()
console.log('绘制完毕')
uni.setStorageSync('openSaveShareImage', true)
this.saveOver()
},
saveOver() {
console.log("绘制----------end")
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'sharePic',
destWidth: this.boxDetail.width * 2, //展示图片尺寸=画布尺寸1*像素比2
destHeight: this.boxDetail.height * 2,
quality: 1,
fileType: 'png',
success: (result) => {
console.log(result.tempFilePath)
uni.saveImageToPhotosAlbum({
filePath: result.tempFilePath,
success: function() {
uni.hideToast()
uni.showToast({
icon: 'none',
title: "图片已下载至【相册】,请打开【相册】查看", // res.tempFilePath
});
},
fail: function() {
uni.hideToast()
uni.showToast({
icon: 'none',
title: "保存失败", // res.tempFilePath
});
}
})
},
}, this);
}, 1000)
},