微信小程序官方解决方案:wxml-to-canvas
1、只能画view,text,image
2、每个元素必须要设置宽高
3、默认是flex布局,可以通过flexDirection: "column"来改变排列方式
4、文字 必须放在text中,放在view中无法显示,且text不支持字体加粗
5、如果你要设置背景颜色,请使用backgroundColor,而非background,border同理~
6、多个absolute元素时,因为没有z-index,template元素自上而下渲染,对应z-index依次增高
7、导出图片过大,可以通过canvasToTempFilePath({fileType, quality})配置里面的quality字段来减小
8、image只支持临时地址和网络地址,不支持base64和本地图片,可以通过writeFile 把base64转成临时地址
如下:
const fs = wx.getFileSystemManager();
let qrcodeBase64 = QRresult.data;
let qrcodeLink = `${wx.env.USER_DATA_PATH}/qrcodeLink.gif`;
fs.writeFile({
filePath: qrcodeLink,
data: qrcodeBase64,
encoding: 'base64',
success: res => {
console.log(res)
shopJson.qrcode = qrcodeLink;
this.renderToCanvas();
},
fail(res) {
console.error(res)
}
})
9、Canvas 初始化问题:为了正确绘制图像,需要确保在调用 canvas.draw() 之前完成所有绘制操作。
10、不支持实时更新:一旦 Canvas 绘制完成,它将成为静态图像,无法实时更新。如果需要实时更新数据可视化图表,可以考虑
使用其他库或组件。
11、长按保存图片问题:wxml-to-canvas 组件默认无法通过长按保存图片功能保存 Canvas 图像。你可能需要自行实现该功能,
并适配不同平台的实现方式。
12、对低版本小程序的兼容性问题:某些较旧的微信小程序版本可能无法完全支持 wxml-to-canvas 组件。在开发之前,请确保目
标用户群体的微信小程序版本。
13、性能问题:复杂的数据可视化图表可能会影响性能,尤其是在绘制大量数据时。尽量优化绘制逻辑,以避免出现卡顿或延迟。
14、与其他组件的兼容性问题:wxml-to-canvas 组件可能与其他小程序组件存在一些兼容性问题,导致样式错乱或布局问题。建
议在使用时进行充分的测试和调试。
15、跨平台问题:wxml-to-canvas 组件目前主要针对微信小程序,可能无法直接适用于其他小程序平台或移动端框架。如果需要
在其他平台上实现类似的功能,可能需要另行寻找适合的解决方案。
16、Canvas 绘图能力限制:由于 Canvas 的绘图能力有限,某些高级的数据可视化需求可能无法直接通过 wxml-to-canvas 组
件实现。在确定方案之前,建议先了解 Canvas 绘图的限制和特性。
17、样式定制问题:某些样式属性或效果可能难以通过 wxml-to-canvas 组件实现,例如阴影、渐变色等。需要根据具体需求考虑
是否能够满足所需的样式效果。
18、开发者工具与真机表现差异:在进行调试和预览时,开发者工具上的表现可能与真机上存在一些差异。建议进行真机测试,以
确保数据可视化图表在不同设备上正常显示。
等等...其他未知问题...
npm install --save wxml-to-canvas
注意:这里有的安装后生成的node_modules还不一样,可直接复制dist里面的即可(如下图)
module.exports = require("widget-ui");
//改为
module.exports = require("../widget-ui/index.js")
"pages": [
{
"path": "pages/xxx",
"style": {
"usingComponents": {
"wxml-to-canvas": "/wxcomponents/wxml-to-canvas/index"
}
}
},
...
]
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"usingComponents": {
"wxml-to-canvas": "/wxcomponents/wxml-to-canvas/index"
}
},
<!-- 测试页面,可复制使用 -->
<template>
<view class="wrap">
<!-- 保存canvas测试 -->
<view class="share-page">
<view class="share-page-box" id="box" v-if="show"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
<wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight"></wxml-to-canvas>
</view>
<view class="share-page-box msg-box" v-else :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
{{ msg }}
</view>
<view class="share-page-btn" @tap="extraImage">
<button class="btn-big" :style="getBtnStyle">保存图片</button>
</view>
</view>
</view>
</template>
<script>
import { wxml, style } from './DomData';
export default {
data() {
return {
show: false, // 是否显示canvas
canvasWidth: 320, // 默认canvas宽高
canvasHeight: 480,
screenWidth: null, // 设备宽度
screenHeight: null, // 设备高度
// name: '',
// pic: '',
// chapter1: '',
// chapter2: '',
widget: null,
msg: '加载中,请稍等...', // 提示语
listData:[
{
type:'1',
title:'标题一',
auther: '张三 2024-01-10',
imgsrc:'https://pic1.zhimg.com/80/v2-58fe538a59f870407b1435bfd45893ed_720w.jpeg',
text:'本报讯(记者 杜晨薇)上海正在建设东方枢纽项目,浦东国际机场与沪通铁路将实现连通,枢纽周边临空产业、重点项目布局正在规划设计中。为落实上海市政府与中国东方航空集团有限公司(以下简称“中国东航”)合作协议要求,充分利用东方枢纽建设发展机遇期进一步深化合作发展,昨天下午,浦东新区政府与中国东航签署深化合作战略协议。'
},
{
type: '2',
title: '标题二',
auther: '张三 2024-01-10',
imgsrc: 'https://pic1.zhimg.com/80/v2-58fe538a59f870407b1435bfd45893ed_720w.jpeg',
text: '本报讯(记者 杜晨薇)上海正在建设东方枢纽项目,浦东国际机场与沪通铁路将实现连通,枢纽周边临空产业、重点项目布局正在规划设计中。为落实上海市政府与中国东方航空集团有限公司(以下简称“中国东航”)合作协议要求,充分利用东方枢纽建设发展机遇期进一步深化合作发展,昨天下午,浦东新区政府与中国东航签署深化合作战略协议。'
},
{
type: '3',
title: '标题三',
auther: '张三 2024-01-10',
imgsrc: 'https://pic1.zhimg.com/80/v2-58fe538a59f870407b1435bfd45893ed_720w.jpeg',
text: '本报讯(记者 杜晨薇)上海正在建设东方枢纽项目,浦东国际机场与沪通铁路将实现连通,枢纽周边临空产业、重点项目布局正在规划设计中。为落实上海市政府与中国东方航空集团有限公司(以下简称“中国东航”)合作协议要求,充分利用东方枢纽建设发展机遇期进一步深化合作发展,昨天下午,浦东新区政府与中国东航签署深化合作战略协议。'
},
{
type: '4',
title: '标题四',
auther: '张三 2024-01-10',
imgsrc: 'https://pic1.zhimg.com/80/v2-58fe538a59f870407b1435bfd45893ed_720w.jpeg',
text: '本报讯(记者 杜晨薇)上海正在建设东方枢纽项目,浦东国际机场与沪通铁路将实现连通,枢纽周边临空产业、重点项目布局正在规划设计中。为落实上海市政府与中国东方航空集团有限公司(以下简称“中国东航”)合作协议要求,充分利用东方枢纽建设发展机遇期进一步深化合作发展,昨天下午,浦东新区政府与中国东航签署深化合作战略协议。'
},
{
type: '5',
title: '标题五',
auther: '张三 2024-01-10',
imgsrc: 'https://pic1.zhimg.com/80/v2-58fe538a59f870407b1435bfd45893ed_720w.jpeg',
text: '本报讯(记者 杜晨薇)上海正在建设东方枢纽项目,浦东国际机场与沪通铁路将实现连通,枢纽周边临空产业、重点项目布局正在规划设计中。为落实上海市政府与中国东方航空集团有限公司(以下简称“中国东航”)合作协议要求,充分利用东方枢纽建设发展机遇期进一步深化合作发展,昨天下午,浦东新区政府与中国东航签署深化合作战略协议。'
},
]
}
},
methods: {
// wxml 转 canvas
renderToCanvas() {
console.log('canvasStyle.widget', this.widget)
const _wxml = wxml(this.listData);
console.log('this.widget', _wxml)
const _style = style(this.screenWidth, this.canvasWidth, this.canvasHeight) //this.canvasHeight
const p1 = this.widget.renderToCanvas({ wxml: _wxml, style: _style })
console.log('renderToCanvas', p1)
p1.then((res) => {
console.log('海报生成成功', res);
wx.hideLoading()
}).catch((err) => {
console.log('生成失败', err)
})
},
// 保存到朋友圈
extraImage() {
if (!this.show) {
wx.showToast({ title: '海报生成失败,无法分享到朋友圈', icon: 'none' })
return
}
wx.showLoading({ title: '海报生成中...' })
const p2 = this.widget.canvasToTempFilePath({ fileType:'jpg', quality :0.5})
let that = this;
p2.then(result => {
let path = result.tempFilePath
wx.getSetting({
success: res => {
wx.hideLoading()
// 非初始化且未授权的情况,需要再次弹窗提示授权
if (res.authSetting['scope.writePhotosAlbum'] != undefined && res.authSetting['scope.writePhotosAlbum'] != true) {
wx.showModal({
title: '是否授权相册权限',
content: '需要获取相册权限,请确认授权,否则无法使用相关功能',
success: res => {
if (res.confirm) {
wx.openSetting({
success: dataAu => {
if (dataAu.authSetting["scope.writePhotosAlbum"] == true) {
wx.showToast({
title: '授权成功',
icon: 'none',
duration: 1000
});
that.saveIMg(path);
} else {
wx.showToast({
title: '授权失败',
icon: 'success',
duration: 1000
});
}
}
});
}
}
});
} else {
// 初始化且未授权,系统默认会弹窗提示授权
// 非初始化且已授权,也会进入这里
that.saveIMg(path);
}
}
});
})
},
// 保存到相册
async saveIMg(tempFilePath) {
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: async (res) => {
wx.showModal({
content: '图片已保存,分享给好友吧!',
showCancel: false,
confirmText: '好的',
confirmColor: '#333',
success: function (res) {
wx.navigateBack({
//返回
delta: 1
});
},
fail: function (res) {
console.log('res', res);
}
});
},
fail: function (res) {
wx.showToast({
title: '您取消了授权',
icon: 'none',
duration: 2000
})
}
});
},
},
onLoad(options) {
console.log('options', options);
// 获取设备信息
wx.getSystemInfo({
success: (res) => {
console.log('屏幕',res)
this.screenWidth = res.screenWidth;
this.canvasWidth = this.screenWidth;
this.canvasHeight = this.screenWidth * 8.5;
console.log('海报高度:', this.canvasHeight)
this.show = true
// 数字容器宽度 动态设置
setTimeout(() => {
wx.showLoading({ title: '海报加载中...' })
this.widget = this.selectComponent('.widget')
this.renderToCanvas()
}, 1000)
}
});
},
}
</script>
<style lang="scss">
.share-page {
background: #cc0202;
position: relative;
overflow: hidden;
// padding: 10rpx;
// min-height: 100vh;
.msg-box {
display: flex;
align-items: center;
text-align: center;
justify-content: center;
}
.share-page-box {
margin: 0 auto;
position: relative;
overflow: hidden;
box-shadow: 0rpx 6rpx 20rpx 6rpx rgba(0, 0, 0, 0.2);
}
.share-page-btn {
margin: 0 10rpx 0 10rpx;
img {
width: 100%;
height: 100%;
}
}
}
</style>
/**
*
* @param {*} listData canvas数据
*/
export const wxml = (listData) => `
<view class="container">
${listData.map(item=>{
return `
<view class="contentWrap">
<view >
<image src="`+ item.imgsrc + `" class="pic1"/>
<text class="name">`+ item.title + `</text>
<text class="subtitle">`+ item.auther + `</text>
</view>
<view class="bottomcss">
<image src="`+ item.imgsrc + `" class="imgbc"/>
<view class="tapContent1">
<text class="tapname">`+ item.text+`</text>
</view>
</view>
</view>`
}).join('')}
</view>
`
/**
*
*
* @param {*} screenWidth 屏幕宽度
* @param {*} canvasWidth 画布宽度
* @param {*} canvasHeight 画布高度
* @param {*} numberWidth 数字宽度,动态设置
* @return {*}
*/
export const style = (screenWidth, canvasWidth, canvasHeight) => {
return {
"container": {
width: canvasWidth,
minHeight: canvasHeight,
position:'relative',
backgroundColor: '#ffffff',
justifyContent: 'center',
alignItems:'center',
overflow: 'hidden'
},
"bottomcss":{
marginTop: 0,
},
"contentWrap":{
position: 'relative',
width: canvasWidth * 0.99,
marginBottom: 20,
marginTop: 5,
marginLeft: 1,
borderRadius: 20,
overflow:'hidden',
backgroundColor: '#333333',
},
"imgbc":{
justifyContent: 'center',
alignItems: 'center',
width: canvasWidth * 0.97,
height: canvasWidth,
marginBottom: 4,
marginLeft: 4,
marginTop: 10,
borderRadius: 20,
overflow: 'hidden',
},
"tapContent1":{
position:'absolute',
top: 0,
left: 0,
},
"tapname":{
fontSize: 18,
color: '#fff',
marginLeft: 15,
marginTop: 50,
overflow: 'hidden',
width: canvasWidth * 0.92,
height: 400,
textAlign: 'left',
},
"name":{
fontSize: 20,
color: '#fff',
marginLeft: canvasWidth * 0.08,
width: canvasWidth * 0.84,
height: 30,
textAlign: 'center',
},
"subtitle":{
fontSize: 14,
color: '#9E9C9C',
marginLeft: canvasWidth * 0.08,
width: canvasWidth * 0.84,
height: 20,
textAlign: 'center',
},
"content": {
fontSize: 14,
color: '#333',
width: canvasWidth * 0.84,
height: screenWidth * 0.15,
marginLeft: canvasWidth * 0.08,
},
"pic1": {
width: canvasWidth * 0.3,
height: screenWidth * 0.3,
marginTop: canvasWidth * 0.1,
marginLeft: canvasWidth * 0.35,
marginBottom: canvasWidth * 0.05,
borderRadius: screenWidth * 0.14,
overflow: 'hidden',
},
"pic2": {
width: canvasWidth ,
height: canvasWidth ,
marginTop:10,
},
"bottom":{
width: canvasWidth,
height: screenWidth * 0.2,
flexDirection: 'row',
justifyContent: 'self-start',
alignItems: 'center',
backgroundColor: '#fafafa',
position: 'absolute',
bottom: 0,
left: 0,
},
"qr": {
width: canvasWidth * 0.14,
height: screenWidth * 0.14,
marginLeft: canvasWidth * 0.04,
marginRight: canvasWidth * 0.04,
},
"msg": {
fontSize: 14,
color: '#a1a1a1',
width: canvasWidth * 0.74,
height: 14,
textAlign: 'left'
},
}
}
?
?