(本文只是个人的学习记录,请大佬指出错误。🙊)最近在工作里,遇到一个微信小程序的简单需求,需要把页面截图并下载。虽然我的第一想法是就不能让用户自己手动截图吗,当然也就自己想想吧😥。之前做过一个生成海报的功能,但跟这次有点不一样,涉及到动态页面数据和样式,所以尝试了其他方法。
目前我用过的方法,大致有三种:
1. 直接用canvas自己手动绘制;
2. 用wxml2Canvas等工具帮助生成canvas对象并生成图片
3. 页面嵌入webview,利用小程序的JSSDK,保存图片.
先说下我的个人总结:像布局和数据不会有变化、不复杂的情况下,直接用canvas会很方便;但如果数据是动态的,页面的一些样式也会随之变化,比较难做适配的话,建议用webview的方法。其实复杂情况下,用方法1也是可以实现的,比如多行数据不知道高度的情况下,可以通过先在页面上显示元素,然后获取所需要的样式参数等。但同时工作量也会大大增加,后期的调试和适配也是个让人头疼的问题,所以弊端还是存在的😖。最后的重点:不推荐使用wxml2Canvas、wxmlToCanvas等工具,坑比较多,某些样式也会变形。下面我对这三个方法简单说一下:
1. 直接使用小程序的canvas生成分享海报
分享海报大致分为三个步骤:一张图作为背景、图上画一个白底圆形、将生成的带参数二维码画到白色圆形里。
首先定义一个canvas,由于图片的比例可能不可知,canvas的宽高根据图片的宽高去确定。
<canvas class='canvas' :style="{height:canvasH+'px',width:canvasW+'px'}"
canvas-id="mycanvas"/>
获取图片的宽高比例的方法,就可以确定你要画的图的尺寸了。(比如可以以屏幕宽度为基础尺寸来决定你的图片尺寸)
<image :src = "imageUrl" mode = "widthFix" @load= "onImgLoad" v-show = "false"> </image>
onImgLoad(e)
{
let imgW = e.detail.width; //图片原始宽度
let imgH = e.detail.height; //图片原始高度
this.scale = imgW / imgH; //比例计算
}
然后,获取后端返回的带参数二维码(base64格式),转为图片保存至本地(canvas不能直接绘制网络图片,背景图也是要先保存在本地)
//声明文件系统
const fs = wx.getFileSystemManager();
//随机定义路径名称
var times = new Date().getTime();
var codeimg = wx.env.USER_DATA_PATH + '/' + times + '.png';
//将base64图片写入
fs.writeFile({
filePath: codeimg,
data: data.data.wxacode, // base64
encoding: 'base64',
success: () => {
//写入成功了的话,新的图片路径就能用了
that.localCodeUrl = codeimg
// 创建canvas图片
that.createNewImg();
}
});
最后一步,就是绘制了
createNewImg(){
let imgH = this.canvasH //前面定义的canvas的高度
let imgW = this.canvasW //canvas的宽度
let ctx = wx.createCanvasContext('mycanvas', this)
// 绘制白色背景
ctx.setFillStyle("#fff")
ctx.fillRect(x, y, imgW, imgH) // context.fillRect(x,y,width,height);
//绘制图片
ctx.drawImage(this.localBackgroundUrl, x, y, imgW, imgH) // 绘制背景图
// 绘制标题
ctx.setFontSize(18)
ctx.setFillStyle('#333')
// 绘制二维码,以图片的宽高,通过比例确定二维码的位置,防止错位
let xyCircle = [1.2, 0.79, 0.64, 0.18]
let xyCode = [1.05, 1.14, 0.31]
// 画出白色圆形背景
ctx.arc(imgW * xyCircle[0], imgH * xyCircle[1] + imgW * xyCircle[2], imgW * xyCircle[3], 0, 2 * Math.PI)
ctx.setFillStyle('#FFFFFF')
ctx.fill()
// 绘制二维码
ctx.drawImage(this.localCodeUrl, imgW * xyCode[0], imgH * xyCode[1], imgW * xyCode[2], imgW * xyCode[2])
// 显示绘制
ctx.draw()
//将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
this.timer = setTimeout(() => {
wx.canvasToTempFilePath({
canvasId: 'mycanvas',
x:x, // 画布区域左上角坐标
y:y, // 画布区域左上角坐标
width:imgW,
height:imgH,
// destWidth: imgW*2, // 决定了保存图片的清晰程度
// destHeight: imgH*2, // 决定了保存图片的清晰程度
success: function(res) {
let tempFilePath = res.tempFilePath
that.loadImagePath = tempFilePath // 本地图片地址,现在可以下载了
},
fail: function(res) {
console.log(res)
},
complete:function(){
that.canvasType = true
}
}, that)
}, 500)
}
2. wxml2Canvas的使用和踩坑记录
该工具是将wxml元素转换成canvas元素,从而生成图片。它支持两种格式,一个是JSON形式,根据type将元素一个个画上去,比如:
{
type: 'rect',
x: 10,
y: 10,
style: {
width: 150,
height: 80,
}
}
这样显然也是比较麻烦的,这显然不符合我的要求。还有一种就是wxml转换的方式,根据wxml里面的样式,自动绘制出你想要的元素模块。我这边就是用的这种方式。
因为不推荐,就上一段简单的代码,详细的用法可以看git地址github.com/wg-front/wx…。
let drawImage = new Wxml2Canvas({
element: 'share', // canvas节点的id,
obj: this, // 在组件中使用时,需要传入当前组件的this
width: this.width, // 宽高
height: this.height,
background: '#FF9652', // 默认背景色
progress(percent) { // 绘制进度
},
finish(url) {
wx.showShareImageMenu({
path: url,
success: function (res) {
wx.showToast({
title: '分享成功',
icon: 'success',
duration: 2500
})
},
})
},
error(res) {
console.log(res);
}
}, this);
let data = {
//直接获取wxml数据
list: [{
type: 'wxml',
class: '.draw_canvas', // 需要绘制的元素,必须加上这个class名
limit: '.outer_class' // 只在此class内的元素才会被绘制
},]
}
//传入数据,画制canvas图片
drawImage.draw(data);
注意点:
1. Wxml2Canvas内容都不显示:
每个需要的绘制的元素,draw_canvas、data-type、data-text或者data-url等缺一不可。
<view style="display: inline-block;width: 100rpx;" class="draw_canvas" data-type="text"
data-text="hello"> hello </view>
2. Wxml2Canvas的文字设置粗体,安卓真机不显示:
好像是小程序的canvas一直存在问题,只有设置了 cts.font = 'normal bold 14px 'font-family'' 才可以。我们可以修改源码中对应设置字体的地方:
this.ctx.font = (`${style.fontWeight > 500 ? 'normal bold' : 'normal'} ${fontSize}px ${style.fontFamily || 'PingFang SC'}`);
还有所有没有设置font的this.ctx.fillText()方法前面,应该是主要在_drawText方法里面,也需要加上这一句。
3. Wxml2Canvas的wxml转换方式里,怎么实现超过两行省略号:
如果直接写上css样式,在这边的是不生效的。但它的JSON格式方法里的text是支持的,所以我们可以在对应设置lineClamp参数的地方,加上一个判断
if (style['-webkit-line-clamp'] !== 'none') { // 如果style的元素里有-webkit-line-clamp属性,则说明需要实现转行省略号功能。
style.lineClamp = style['-webkit-line-clamp']
}
同时,在 _getWxml 方法获取 style 内元素的数组 computedStyle 里需要加上 -webkit-line-clamp ,这样才能被获取到。
4. Wxml2Canvas的图片有时候不显示:
网络图片最好是下载到本地,在确保图片下载完之后再去生成图片,这边可以写个简易的promise方法来实现。
5. 奇怪的现象,有时候某些元素不显示,可能是某些行或者某些字,再重新进几次可能又好了。
简单看了下源码也没找到原因,有大神知道可以告知下(主要是我菜,懒得一行行去看了)。黑科技(不推荐):偶然间发现,页面上下滑动的距离,可以影响到此情况。直接上代码
if (resPhone.platform === 'ios') { // ios的情况下,生成截图前,页面需要先置顶
wx.pageScrollTo({
scrollTop: 0
})
} else if (resPhone.platform === 'android') { // android的情况下,生成截图前,页面需要先到底部
wx.pageScrollTo({
scrollTop: res[0].height // 当前页面的高度
})
}
3. 在小程序里使用webview生成截图
在文章开头就讲到了不推荐方法2,主要原因就是真机生成的图片,变形很严重,样式也会变化,总之就不是我们页面上的样式,再加上坑很多,还是建议别用了。
用webview其实很简单,我这边使用了webview的bindmessage方法和微信小程序的JSSDK、html2canvas实现的。
1. 引入JSSDK,此处涉及的功能不需要配置wx.config()
npm i weixin-js-sdk --save
import wx from 'weixin-js-sdk'
2. 引入html2canvas
npm install --save html2canvas //useCORS、allowTaint解决图片跨域
3. 使用
<web-view :src="url" @message="bindmessage"></web-view> // 网页向小程序 postMessage 时,会在特定时机(小程序后退、组件销毁、分享)触发并收到消息。此处uniapp的用法
bindmessage(e){
var save = wx.getFileSystemManager(); // 获取文件管理器对象
var number = Math.random();
save.writeFile({
filePath: wx.env.USER_DATA_PATH + '/pic' + number + '.png', // 表示生成一个临时文件名
data: e.detail.data[0].replace('data:image/png;base64,',''), // 此方法需要将data:image/png;base64,去掉
encoding: 'base64',
success: res => {
wx.saveImageToPhotosAlbum({
filePath: wx.env.USER_DATA_PATH + '/pic' + number + '.png',
success: function (res) {
wx.showToast({
title: '保存成功',
})
},
fail: function (err) {
console.log(err)
}
})
}, fail: err => {
}
})
}
参数放到url里传给web端,web端获取到参数后,调接口拿数据,然后触发saveImage方法。
saveImage(divText)
{
let canvasID = this.$refs[divText];
html2canvas(canvasID, {useCORS: true, allowTaint: true,}).then(canvas => {
let base = canvas.toDataURL('image/png')
wx.miniProgram.postMessage({data: base}) // 注册postMessage方法,参数是base64格式
wx.miniProgram.navigateBack() // 小程序返回上级,通过销毁页面,触发postMessage
});
}
4. 踩坑
- 在
webview中使用html2canvas时,在IOS15及以上的系统里,不支持rem、根据屏幕宽度决定尺寸的适配方案。由canvas.toDataURL('image/png')方法得到的,不是正确的base64数据。 可以在调用方法之前,先将需要的DOM的rem转换成px。 - 微信小程序的PC端不支持
postMessage方法,可以选择通过wx.config()获取wx.downloadImage方法的权限来实现本地文件下载。
总结:
其实,如果有管理后台的话,可以在信息新增或修改的时候,就将图片生成并上传,此时只需要连同图片地址存起来就可以了,这种方法也是很省时省力的。上面介绍的都是一些很简单的内容,也是我本身的一个记录和学习过程。希望有大佬指出错误,有更好的方法的话,欢迎补充!😂