页面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
><view class="mask" v-show="isShow">
<view class='board'>
<view class="heart">
<span class="sure" @click="isShow=false">×</span>
</view>
<canvas @longpress="saveHomepage" :style="'width:'+canvasWidth+'px;height:'+ canvasHeight+'px;'"
canvas-id="myCanvas"></canvas>
</view>
></view>

><view class="foot-icon-card" @click="addCanvas(activity)">
<image src="../../static/icons/haibao.svg"> </image>
<text>生成海报</text>
></view>

data属性定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>canvasWidth: 300,
>canvasHeight: 500,
>isShow: false,
>img_pop: '',
>poster: ''
>imageList: [
"https://s1.ax1x.com/2023/06/02/p9zHfp9.jpg",
"https://s1.ax1x.com/2023/06/02/p9zHT0K.jpg",
"https://s1.ax1x.com/2023/06/02/p9zHhlR.jpg",
"https://s1.ax1x.com/2023/06/02/p9zHRfJ.jpg",
"https://s1.ax1x.com/2023/06/02/p9zH2Y4.jpg",
"https://s1.ax1x.com/2023/06/02/p9zHom6.jpg",
"https://s1.ax1x.com/2023/06/02/p9zH5Ox.jpg",
"https://s1.ax1x.com/2023/06/02/p9zH461.jpg",
]

methods实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
>//文字自动换行显示,超出指定范围用...代替显示

>textSplit(ctx, text, lineWidth, lines) {
const charWidths = Array.from(text, char => ctx.measureText(char).width);

let textArr = [];
let c_text = '';
let lineCount = 0;

for (let i = 0; i < text.length; i++) {
const charWidth = charWidths[i];

if (ctx.measureText(c_text).width + charWidth < lineWidth) {
c_text += text[i];
} else {
lineCount++;
if (lineCount === lines) {
const lastLineWidth = ctx.measureText(c_text).width;
c_text = c_text.slice(0, -1);
while (ctx.measureText(c_text + '...').width > lineWidth) {
c_text = c_text.slice(0, -1);
}
c_text += '...';
textArr.push(c_text);
return textArr;
} else {
textArr.push(c_text);
c_text = text[i];
}
}
}

// 当长度小于指定的 `lineWidth` 时,直接返回传入的文本内容
if (textArr.length === 0) {
return [text];
}

// 添加最后一行文本内容
textArr.push(c_text);

// 如果超过了指定行数,则修改最后一行内容,并返回修改后的文本数组。
if (textArr.length > lines) {
const lastLineWidth = ctx.measureText(c_text).width;
c_text = textArr.pop();
while (ctx.measureText(c_text + '...').width > lineWidth) {
c_text = c_text.slice(0, -1);
}
c_text += '...';
textArr.push(c_text);
}

return textArr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>//图片网络路径转换为临时路径
>getImgUrl(img) {
return new Promise((result, rej) => {
wx.getImageInfo({
src: img,
success: (res) => {
console.log(res, "获取");
return result(res.path)
},
fail: (error) => {
rej(error)
}
})
})
>},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
async addCanvas(item) {
this.isShow = true
this.foot = 'close'
uni.showLoading({
title: '正在生成海报'
})
let ctx = wx.createCanvasContext('myCanvas')
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)

// 封面图
if (item.avatar) {
ctx.drawImage(await this.getImgUrl(item.avatar), 0, 0, this.canvasWidth, 200)
} else {
//随机背景图
ctx.drawImage(await this.getImgUrl(this.imageList[Math.floor(Math.random() * 8)]), 0, 0, this
.canvasWidth, 200)
}

//标题
ctx.font = `normal 20px `
ctx.fillStyle = '#000'
let nameText = this.textSplit(ctx, item.name, this.canvasWidth - 20, 1)
nameText.forEach(r => {
ctx.fillText(r, 15, 220)
})

//活动内容
let lineHeight = 25; //文字行高
if (item.description.includes('\n')) {
let flag = 0;
let height = 0
const arrDesc = item.description.split("\n");
let lastText = ''
outerLoop: for (var i = 0; i < arrDesc.length; i++) {
let text = this.textSplit(ctx, arrDesc[i], this.canvasWidth - 30, 7)
interLoop: for (var j = 0; j < text.length; j++) {
height = flag * lineHeight + 260
flag++;
if (flag > 7) break outerLoop;

ctx.font = `${fontSize}px normal`
ctx.fillStyle = '#636363'
ctx.fillText(text[j], 15, height)
lastText = text[j]
}
}
if (!lastText.includes('...')) {
ctx.font = `${fontSize}px normal`
ctx.fillStyle = '#636363'
ctx.fillText('...', 15, height)
}
} else {
let text = this.textSplit(ctx, item.description, this.canvasWidth + 30, 7)
let lastText = ''
let height = 0
console.log("ctx.measureText(char).width===" + ctx.measureText('还').width);
text.forEach((r, index) => {
ctx.font = `${fontSize}px normal`
ctx.fillStyle = '#636363'
ctx.fillText(r, 15, index * lineHeight + 260)
console.log(260 + index * lineHeight);
height = 260 + index * lineHeight
lastText = r
})
if (!lastText.includes('...')) {
ctx.font = `${fontSize}px normal`
ctx.fillStyle = '#636363'
ctx.fillText('...', 15, height + 10)
}
}

//地点
ctx.font = `normal 14px `
ctx.fillStyle = '#000'
ctx.fillText('活动地点:', 15, 350)
let textAdress = this.textSplit(ctx, item.address, this.canvasWidth - 100, 2)
textAdress.forEach((r, index) => {
ctx.font = `normal 14px `
ctx.fillStyle = '#636363'
ctx.fillText(r, 15, 370 + index * lineHeight)
})

//时间
ctx.font = `normal 14px `
ctx.fillStyle = '#000'
ctx.fillText('时间:', 15, 420)
ctx.font = `normal 14px `
ctx.fillStyle = '#000'
ctx.fillText(item.start_date + " " + item.start_time, 15, 440)


// 二维码
ctx.drawImage(this.img_pop, this.canvasWidth / 2 + this.canvasWidth / 4, 340, 70, 70)

// 底部文字
ctx.font = 'normal 12px '
ctx.fillStyle = '#9C9C9C'
ctx.fillText('长按识别或保存图片', this.canvasWidth / 3, 470)

//开始绘画
await ctx.draw(true, res => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: res => {
this.poster = res.tempFilePath
uni.hideLoading()
},
fail: err => {
uni.hideLoading()
}
}, this)
}, 1000)
})
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
>// 判断是否有写入相册的权限
saveHomepage() {
let that = this;
//判断当前小程序是否有保存相册的权限
wx.getSetting({
success(res) {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success(res) {
that.startSaveImage();
},
fail() { //这里是用户拒绝授权后的回调
wx.showModal({
content: '请允许相册权限,拒绝将无法正常使用小程序',
showCancel: false,
success() {
wx.openSetting({
success(settingdata) {
if (settingdata.authSetting[
"scope.writePhotosAlbum"
]) {} else {
console.log("获取权限失败")
}

}
})
}
})
}
})
} else {
that.startSaveImage();
}
}
})
>},

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>//保存本地

>startSaveImage() {
uni.saveImageToPhotosAlbum({
filePath: this.poster,
success: (res) => {
uni.showToast({
title: '保存成功',
icon: 'none',
duration: 4000
})
this.$refs.popup.close()
}
})
>},

css样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
>.mask {
position: fixed;
top: 0;
left: 0;
z-index: 998;
width: 100vw;
height: 100%;
background-color: #000000;
overflow: hidden;
}

.board {
animation: myframe 0.3s;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

z-index: 999;
}

.sure {
text-align: center;
display: block;
width: 46rpx;
margin: 0 auto;
font-size: 32rpx;
color: #fff;
height: 46rpx;
border-radius: 50%;
line-height: 44rpx;
border: 1px solid #ffffff;
font-family: '楷体';
}
>.heart {
position: relative;
bottom: -540px;
}

效果图

效果图

参考:uniapp点击生成商品海报、下载海报、分享海报
微信小程序Canvas绘制主页保存到手机相册