最近做了一个问卷类的小程序,其中的结果页想让用户进行朋友圈分享转发,网上搜索资料,得出解决思路,用 canvas 将页面绘制生成图片,然后保存到手机相册,最终效果如下:
在这里我只写页面里关于 canvas 生成图片并进行保存这个流程的相关代码,并且会在我踩过的坑那里进行具体的讲述。废话不多说直接上干货
wxml
分享助力
保存到相册
wxss
.share {
width: 356rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
color: #e73322;
font-size: 32rpx;
border-radius: 37rpx;
background: #fff;
margin: 70rpx auto 0;
}
.share-img {
width: 31rpx;
}
.canvas-box {
position: fixed;
top: 99999px;
left: 0;
width: 100%;
}
.shareimg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mask {
position: fixed;
z-index: 1;
background: rgba(0, 0, 0, .5);
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.share-btn {
position: fixed;
width: 356rpx;
bottom: 50rpx;
left: 197rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
color: #e73322;
font-size: 32rpx;
border-radius: 37rpx;
background: #fff;
z-index: 999;
}
js
1. 设置 data 相关默认值
-
canvasHidden: true
设置使 canvas 隐藏,因为 canvas 是原生组件,拥有最高层级,如果不隐藏,会影响页面正常使用 -
hiddenImg: true
设置使黑色遮罩、保存按钮和显示生成后的图片隐藏
data: {
canvasHidden: true,
wxappName: "来「 老字号文化影响力 」测试你的知识等级",
hiddenImg: true
},
2. 在 onLoad 方法中准备需要用到的参数
这里需要注意一点我踩过的坑,
- 由于小程序的canvas 不能使用网络图片,所以缓存中的头像不能直接用,需要使用
wx.downloadFile
方法将头像路径存储为临时路径,以供 canvas 使用,由于之前不知道这个,所有折腾半天,画出来的图片就是没有头像 - 这里获取设备宽度、高度以及设备像素比也非常重要,用于最后显示图片的大小以及canvas画图过程中的宽高显示
onLoad: function (o) {
var that = this;
//读取缓存,获取微信头像和昵称
wx.getStorage({
key: 'user',
success: function (res) {
var nickName = res.data.nickName,
avatarUrl = res.data.avatarUrl;
that.setData({
nickName: nickName,
})
// 由于canvas不能使用网络图片,所以此处进行头像临时路径存储
wx.downloadFile({
url: avatarUrl,
success: (res) => {
that.setData({
avatarUrl: res.tempFilePath,
})
},
});
}
})
//获取用户设备信息,屏幕宽度
wx.getSystemInfo({
success: res => {
that.setData({
screenWidth: res.screenWidth,
winHeight: res.windowHeight,
ratio: res.pixelRatio
})
}
})
},
3. 编写 canvas 绘制方法
- 第一步显示画板,配置需要显示的元素
这里需要注意
unit = that.data.screenWidth / 375
用于使用像素值进行绘制后进行手机不同机型大小的适配,context = wx.createCanvasContext('share')
用于指定要绘制的 canvas,其余的参数都是我项目中需要用到的,各位童鞋可以根据自己的需求进行配置
//定义的保存图片方法
share: function () {
wx.showLoading({
title: '图片生成中...',
})
var that = this;
//设置画板显示,才能开始绘图
that.setData({
canvasHidden: false
})
var res = that.data.res.result;
var resImg;
switch (res) {
case 1:
resImg = '/images/sdj.png';
break;
case 2:
resImg = '/images/js.png';
break;
case 3:
resImg = '/images/jr.png';
break;
case 4:
resImg = '/images/gs.png';
break;
case 5:
resImg = '/images/xc.png';
break;
}
var unit = that.data.screenWidth / 375;
var ratio = that.data.ratio;
var screenWidth = that.data.screenWidth;
var winHeight = that.data.winHeight;
var bg = "/images/bg.png"
var avatarUrl = that.data.avatarUrl;
var bgleavel = "/images/bg-leavel.png";
var qrcode = "/images/qrcode.jpg";
var nickName = that.data.nickName;
var context = wx.createCanvasContext('share');
var idnum = that.data.res.id;
var num = idnum.toString();
var length = num.length;
var left;
switch (length) {
case 2:
left = 375 - 208 - length * 26
break;
case 3:
left = 375 - 208 - length * 24
break;
case 4:
left = 375 - 208 - length * 20
break;
case 5:
left = 375 - 208 - length * 19
break;
default:
left = 375 - 208 - length * 18
break;
}
var wxappName = that.data.wxappName;
- 第二步开始绘制将所需元素逐一绘制到画板上
这里我有踩到的坑,就是图片绘制完成后,在手机上显示非常模糊,多番查找折腾后,发现以下几个地方设置好之后就OK了
width: screenWidth
设定指定的画布区域的宽度
height: winHeight
设定指定的画布区域的高度
destWidth: ratio * screenWidth
设定输出图片宽度
destHeight: ratio * winHeight
设定输出图片高度
quality: 1,
设定图片质量
destWidth 和 destHeight 需要设置为width 和height 的 2倍或以上才能让图片清晰,而现在的智能手机设备像素比一般都在2以上,所以这里直接用 ratio 来进行设置
图片绘制完成且临时路径生成之后,打开隐藏的遮罩层和保存按钮以及供用户浏览的生成之后的图片
// 绘制红色背景
context.drawImage(bg, 0, 0, that.data.screenWidth, winHeight)
// 绘制头像
var avatarurl_width = unit * 75; //绘制的头像宽度
var avatarurl_heigth = unit * 75; //绘制的头像高度
var avatarurl_x = unit * 150; //绘制的头像在画布上的位置
var avatarurl_y = unit * 35; //绘制的头像在画布上的位置
context.save();
//先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针
context.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
context.clip(); //画好了圆 剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
context.drawImage(avatarUrl, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 将头像放到绘制好的圆中
context.restore(); //恢复之前保存的绘图上下文状态 还可以继续绘制.
// 绘制昵称
context.setFontSize(14)
context.setFillStyle('#fff')
context.setTextAlign('center')
context.fillText(nickName, unit * 187, unit * 135)
// 绘制知识等级背景图
context.drawImage(bgleavel, unit * 110, unit * 165, unit * 312 / 2, unit * 307 / 2)
context.drawImage(resImg, unit * 160, unit * 180, unit * 110 / 2, unit * 238 / 2)
// 绘制第几位宣传者
context.setFontSize(16)
context.setFillStyle('#fff')
context.setTextAlign('right')
context.fillText('你是第', left * unit, unit * 382)
context.setFontSize(24)
context.setFillStyle('#fff')
context.setTextAlign('center')
context.fillText(num, unit * (left + length * 12), unit * 382)
context.setFontSize(16)
context.setFillStyle('#fff')
context.setTextAlign('left')
context.fillText('位老字号文化传播大使', unit * (left + length * 24), unit * 382)
// 绘制二维码
context.drawImage(qrcode, unit * 138, unit * 410, unit * 204 / 2, unit * 204 / 2)
// 绘制二维码下部文字
context.setFontSize(12)
context.setFillStyle("#fff")
context.setTextAlign("center")
context.fillText("长按识别小程序", unit * 187.5, unit * 540)
context.fillText(wxappName, unit * 187.5, unit * 560)
//把画板内容绘制成图片,并回调 画板图片路径
context.draw(false, function () {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: screenWidth,
height: winHeight,
destWidth: ratio * screenWidth,
destHeight: ratio * winHeight,
canvasId: 'share',
quality: 1,
success: function (res) {
that.setData({
shareImgPath: res.tempFilePath,
hiddenImg: false,
pathRes: res
})
if (!res.tempFilePath) {
wx.showModal({
title: '提示',
content: '图片绘制中,请稍后重试',
showCancel: false
})
}
wx.hideLoading()
}
})
});
},
4. 编写保存到手机相册方法
这里没什么好说的,就是调用微信提供的API 进行图片保存,成功或者失败之后,进行相应提示并将一开始在data中设置的需要默认隐藏的元素进行隐藏,然后就OK了,打开手机相册就可以看到完成的图片进行朋友圈分享了。
save: function () {
var that = this;
var res = that.data.pathRes;
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
//保存成功失败之后,都要隐藏画板,否则影响界面显示。
success: (res) => {
wx.showToast({
title: '保存成功',
icon: 'none',
duration: 1500,
mask: false,
success: function () {
that.setData({
canvasHidden: true,
hiddenImg: true
})
}
});
},
fail: (err) => {
wx.showToast({
title: '保存失败',
icon: 'none',
duration: 1500,
mask: false,
success: function () {
that.setData({
canvasHidden: true,
hiddenImg: true
})
}
});
}
})
}
最后写一下用到的API吧,常用的就不写了主要写一下我自己平时不怎么经常用到的
-
wx.downloadFile
用于将网络图片生成临时路径,这里也有一个坑,需要在小程序公众平台将腾讯的wx.qlogo.cn
这个域名设置为合法域名,否则会报错,在之后的绘制中图片尽量用本地路径 -
wx.createCanvasContext
其中在画布中进行绘制的时候如果有什么不明白的,可以到 w3cschool 看看,链接是直接跳转到 canvas 绘图这一节的 -
wx.canvasToTempFilePath
将画板内容绘制成图片的方法,需要注意上面提到的影响手机图片清晰度的参数,其中所有参数具体配置以及含义,可以直接到 官方文档 查看 -
wx.saveImageToPhotosAlbum
保存图片到本地的API,这个没有难点,不多说了
整篇文章看着代码多,其实用到的不常见的API也就这几个,注意一下文章中我踩过的那几个坑,相信你可以开发出一个完美的带小程序码的用于分享到朋友圈的图片了。
========================================================================================================================
2020年4月16日 补充
1、字体加粗
通过文字多次绘制可以模拟字体的加粗
2、标题文字过长加省略号
通过 measureText
方法获取标题长度,与自己实际展示标题长度做判断,循环进行截取,直到满足条件为止
var titleWidth = context.measureText(title).width;
context.fontSize = 16 * unit;
if(titleWidth > (320*unit)){
while (titleWidth > (320*unit)) {
title = title.substring(0, (title.length - 1));
titleWidth = context.measureText(title).width;
// console.log(title,titleWidth,i,title.length,(title.length - 1))
}
title = title + '...';
}