uniapp、小程序中使用canvas绘制海报,长按可保存。 最近接受到一个海报分享的功能,因为后台还没有完成,所以二维码那块暂时用头像替代。
<template>
<view class="vote-pull">
<canvas
class="vote-canvas"
id="vote-canvas"
canvas-id="vote-canvas"
:style="{ display: canvasImg ? 'none' : 'block' }"
></canvas>
<image :show-menu-by-longpress="true" :src="canvasImg" class="canvas-img" />
</view>
</template>
<script>
var util = require("../../../utils/util.js");
export default {
data() {
return {
idolInfo: {},
votes: 0,
screenWidth: 0,
screenHeight: 0,
commonWidth: 0,
commonPadding: 0,
benchWidth: 390,
canvasImg: ''
};
},
onLoad() {
uni.setNavigationBarTitle({
title: "应援拉票",
});
this.loadData();
this.loadIdolInfo();
this.createVoteCanvas();
},
methods: {
loadData() {
const systemInfo = uni.getSystemInfoSync();
this.screenWidth = systemInfo.screenWidth;
this.screenHeight = systemInfo.windowHeight;
if (systemInfo.windowHeight <= 650) {
this.screenHeight = this.screenHeight * 1.25;
} else if (systemInfo.windowHeight <= 610) {
this.screenHeight = this.screenHeight * 1.3;
}
this.commonWidth = parseInt(this.screenWidth * 0.84);
this.commonPadding = parseInt(this.screenWidth * 0.08);
},
loadIdolInfo() {
this.idolInfo = uni.getStorageSync("idolInfo");
util
.getScaleImageSize(this.idolInfo.img, 0.8)
.then((res) => {
this.idolInfo.height = res.height;
})
.catch((err) => {
console.log(err);
});
},
loadCanvasImg() {
const query = uni.createSelectorQuery();
query.select('#vote-canvas').node().exec((res) => {
const canvasNode = res[0].node;
// 将canvas内容转换为临时文件路径
uni.canvasToTempFilePath({
canvasId: 'vote-canvas',
success: (res) => {
this.canvasImg = res.tempFilePath;
uni.hideLoading();
},
fail: (err) => {
uni.hideLoading();
console.log('转换失败', err);
}
}, canvasNode);
});
},
async createVoteCanvas() {
uni.showLoading();
const w = this.getSw;
const ctx = uni.createCanvasContext("vote-canvas", this);
ctx.save();
ctx.setFillStyle("#C5EBF1");
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
ctx.restore();
ctx.save();
this.roundRect(
ctx,
this.commonPadding + w(30),
this.commonPadding - w(15),
this.commonWidth,
this.commonWidth + w(105),
32
);
ctx.rotate((6 * Math.PI) / 180);
ctx.setFillStyle("#10BBCE");
ctx.fill();
ctx.restore();
const main = {
width: this.commonWidth,
height: this.commonWidth + w(100),
left: this.commonPadding,
top: this.commonPadding + w(10),
radius: 32,
};
this.roundRect(
ctx,
main.left,
main.top,
main.width,
main.height,
main.radius
);
ctx.setFillStyle("#FFFFFF");
ctx.fill();
ctx.font = "normal bold 16px Arial,sans-serif";
ctx.fillStyle = "#3D3D3D";
ctx.fillText(
`编号: ${this.idolInfo.number}`,
this.commonPadding + w(20),
this.commonPadding + w(46)
);
const circle = {
width: util.toDecimal(this.commonWidth * 0.68),
height: util.toDecimal(this.commonWidth * 0.68),
top: util.toDecimal(
this.commonPadding + w(10) + this.commonWidth * 0.16
),
left: util.toDecimal(this.commonPadding + this.commonWidth * 0.16),
};
ctx.save();
this.roundRect(
ctx,
circle.left,
circle.top,
circle.width,
circle.height,
circle.width / 2
);
ctx.strokeStyle = "#0CBCCF";
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
const img = await this.getImageInfo(this.idolInfo.img);
const image = {
src: img,
width: util.toDecimal(this.commonWidth * 0.62),
height: util.toDecimal(this.commonWidth * 0.62),
top: util.toDecimal(
this.commonPadding + w(10) + this.commonWidth * 0.19
),
left: util.toDecimal(this.commonPadding + this.commonWidth * 0.19),
};
this.roundRectImg(
ctx,
image.left,
image.top,
image.width,
image.height,
image.width / 2,
image.src
);
ctx.font = "normal bold 28px Arial,sans-serif ";
ctx.fillStyle = "#333333";
ctx.textAlign = "center";
const name = {
left: this.screenWidth / 2,
top: circle.top + circle.height + w(52),
};
ctx.fillText(this.idolInfo.name, name.left, name.top);
ctx.font = "16px Arial";
ctx.fillStyle = "#4A4A4A";
const declaration = {
width: circle.width,
left: name.left,
top: name.top + w(42),
};
this.getWarpText(ctx, this.idolInfo.declaration, declaration.left, declaration.top, declaration.width, w(20), 3);
const congratulate = {
width: this.commonWidth,
height: w(86),
left: this.commonPadding,
top: main.top + main.height + w(38),
radius: w(18)
}
this.roundRect(ctx, congratulate.left, congratulate.top, congratulate.width, congratulate.height, congratulate.radius);
ctx.setFillStyle("#10BBCE");
ctx.fill()
ctx.font = "normal bold 72px Arial,sans-serif";
ctx.fillStyle = "#FFFFFF";
const symbolTop = {
left: congratulate.left + w(72),
top: congratulate.top + w(62),
};
ctx.fillText('“', symbolTop.left, symbolTop.top);
ctx.font = "18px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
const textTop = {
left: name.left,
top: symbolTop.top - w(32),
};
ctx.fillText('你一票,我一票,', textTop.left, textTop.top);
ctx.font = "18px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
const textBottom = {
left: name.left,
top: textTop.top + w(32),
};
ctx.fillText(`${this.idolInfo.name.slice(-2)}明天就出道。`, textBottom.left, textBottom.top);
ctx.font = "normal bold 72px Arial,sans-serif ";
ctx.fillStyle = "#FFFFFF";
const symbolBottom = {
left: textBottom.left + w(84),
top: textBottom.top + w(36),
};
ctx.fillText('”', symbolBottom.left, symbolBottom.top);
const longpressTip = {
left: this.commonPadding,
top: congratulate.top + congratulate.height + w(52)
}
ctx.font = "16px Arial";
ctx.fillStyle = "#687173";
ctx.textAlign = "left";
ctx.fillText('快来长按识别二维码', longpressTip.left, longpressTip.top);
const vote = {
width: util.toDecimal(this.commonWidth * .54),
height: w(40),
left: this.commonPadding,
top: longpressTip.top + w(24)
}
ctx.save();
ctx.setFillStyle("#10BBCE");
ctx.fillRect(vote.left, vote.top, vote.width, vote.height);
ctx.restore();
const voteTxt = {
left: vote.left + w(24),
top: vote.top + w(26)
}
ctx.font = "normal bold 18px Arial,sans-serif ";
ctx.fillStyle = "#FFFFFF";
ctx.fillText('请为TA投票 ➤', voteTxt.left, voteTxt.top);
const qrcodeImg = await this.getImageInfo(this.idolInfo.img);
const qrwidth = util.toDecimal(this.commonWidth - vote.width - this.commonWidth * .12)
const qrcode = {
width: qrwidth,
height: qrwidth,
left: util.toDecimal(this.commonPadding + vote.width + this.commonWidth * .12),
top: longpressTip.top - w(30)
}
this.roundRectImg(ctx, qrcode.left, qrcode.top, qrcode.width, qrcode.height, util.toDecimal(qrcode.width / 2), qrcodeImg)
ctx.draw();
this.loadCanvasImg();
},
getWarpText(canvas, text, x, y, maxWidth, lineHeight, maxLine) {
// 对入参的类型进行检测
if (
typeof text != "string" ||
typeof x != "number" ||
typeof y != "number" ||
typeof lineHeight != "number"
) {
throw new Error("参数传入出错");
}
//如果最大宽度未定义 默认为canvas宽度
if (typeof maxWidth == "undefined") {
maxWidth = canvas && canvas.width;
}
if (typeof lineHeight == "undefined") {
lineHeight =
(canvas.canvas &&
parseInt(window.getComputedStyle(canvas.canvas).lineHeight)) ||
parseInt(window.getComputedStyle(document.body).lineHeight);
}
let arrText = text.split("");
let line = "";
let lines = [];
let lastLine = "";
let ellipsis = canvas.measureText("...");
let ellipsisWidth = ellipsis.width;
for (let n = 0; n < arrText.length; n++) {
//每个循环累加字符
let testLine = line + arrText[n];
//检测累加字符 获取累加字符的高度和宽度
let metrics = canvas.measureText(testLine);
let testWidth = metrics.width;
let lineWidth = canvas.measureText(line).width;
// 如果当前添加行是最后一行 则替换最后一个字符为"..." 判断长度是否需要删去最后一个字符
if (
maxLine &&
maxLine - 1 === lines.length &&
lineWidth + ellipsisWidth > maxWidth
) {
line = line.slice(0, line.length - 1) + "...";
testWidth = lineWidth + ellipsisWidth;
}
//如果累加字符的宽度大于定义的绘制文本最大宽度 则绘制累加字符的文本 并且设置换行间距再次进行绘制
if (testWidth > maxWidth && n > 0) {
lastLine = line;
lines.push({
text: line,
x: x,
y: y,
});
if (maxLine && maxLine <= lines.length) {
break;
}
line = arrText[n];
y += lineHeight;
} else {
line = testLine;
}
}
if (lastLine !== line) {
lines.push({
text: line,
x: x,
y: y,
});
}
for (let i = 0; i < lines.length; i++) {
const item = lines[i];
canvas.fillText(item.text, item.x, item.y);
}
return lines;
},
getSw(num) {
const rate = util.toDecimal(num / this.benchWidth);
return util.toDecimal(rate * this.screenWidth);
},
roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
},
roundRectImg(ctx, x, y, width, height, radius, img) {
ctx.save();
this.roundRect(ctx, x, y, width, height, radius);
ctx.clip();
ctx.drawImage(img, x, y, width, height);
ctx.restore();
},
getImageInfo(path) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: path,
success: (res) => {
resolve(res.path);
},
fail: (err) => {
reject(err);
},
});
});
},
},
};
</script>
<style scoped>
.vote-pull,
.vote-canvas,
.canvas-img {
width: 100%;
height: 100%;
}
@media screen and (max-height: 610px) {
.vote-pull {
height: 130vh;
}
}
@media screen and (max-height: 650px) {
.vote-pull {
height: 125vh;
}
}
.share-download {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 42rpx;
}
.share-download image {
width: 92rpx;
height: 92rpx;
margin-bottom: 22rpx
}
.share-download span {
font-size: 24rpx;
}
</style>