目录
前言:承接上文的html2canvas踩坑之后,实在是坑的需求要延期了哈,所以还是老老实实的用canvas来话吧。(心酸啊心酸)
1. 绘制结果以及分析

从上图可以看出,其实我们只要做两件事情,第一是用canvas画图,第二是用canvas画文字,调用这两个api就好了!
2. canvas
好了,我们现在要开始了,首先,让我们把这个坐标系给建起来吧,
<canvas class="poster-canvas" type="2d" id="posterQuick"></canvas>
其次我们要拿到它,然后拿到它的渲染上下文
let canvas = document.getElementById('posterQuick'); // 拿到这个canvas
let ctx = this.canvas.getContext('2d'); // 拿到它的渲染上下文
// 给这个画布规定好它的宽高
canvas.width = 375;
canvas.height =756;
3. 封装画图和画文字方法
接下来我们就要开始画图了,让我们封装一下,把上下文,图片路径,图片起始坐标和宽高作为参数传入
drawImage(ctx, path, dx, dy, dw, dh) {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = path;
image.onload = () => {
ctx.drawImage(image, dx, dy, dw, dh);
resolve();
};
image.onerror = () => {
reject();
};
});
},
同理,我们再封装一个绘画文字的方法,但是这个绘画文字的我们要加个options对象,在Options中传入对文字的一些设置,比如加粗,颜色,行高,字体大小,最大宽度等,还有首行缩进与换行
fillText(ctx, text, dx, dy, options = {}) {
let {
color = '#000000', // 字体颜色
fontSize = 24, // 字体大小
lineHeight = undefined, // 行高
maxWidth = 375, // 最大宽度
maxLine = 0,
align = 'left', // 文字对齐方式
bold = false, // 是否加粗
lastHidden = false,
indent = 0, // 缩进
} = options;
lineHeight = lineHeight || fontSize;
ctx.fillStyle = color; // 设置字体颜色
ctx.textAlign = align; // 设置文字对齐方式
// 是否加粗
if (bold) {
ctx.font = `bold ${fontSize}px sans-serifl`;
} else {
ctx.font = `${fontSize}px sans-serif`;
}
let words = text.split('');
let line = '';
let lines = [];
for (let i = 0; i < words.length; i++) {
let testLine = line + words[i] + '';
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
// 首行缩进处理
if (!lines.length && indent) {
testWidth = testWidth + indent;
}
// 换行处理
if (testWidth > maxWidth) {
lines.push(line);
line = words[i] + ' ';
} else {
line = testLine;
}
}
// 绘画文字
lines.push(line);
for (let j = 0; j < lines.length; j++) {
if (j <= 0 && indent) {
ctx.fillText(lines[j], dx + indent, dy + j * lineHeight);
} else {
ctx.fillText(lines[j], dx*this.dpr, dy + j * lineHeight);
}
}
},
4. 调用方法和传参
async makePoster() {
this.canvas = document.getElementById('posterQuick');
let ctx = this.canvas.getContext('2d');
this.imgHeight = this.$refs.urlInfo.offsetHeight || 756; // 获取图片的渲染高度
this.canvas.width =375;
this.canvas.height = this.imgHeight;
await this.drawImage(
ctx,
this.posterList[picIndex].imgurl,
0,
0,
375,
this.imgHeight,
);
await this.fillText(ctx, this.name, 43, 218, {
fontSize: 16,
color: '#222222',
bold: true,
});
await this.fillText(ctx, this.postersConf.message, 43, 266, {
fontSize: 13,
color: '#333333',
maxWidth: 290,
lineHeight: 20,
indent: 20,
// maxLine: 5,
});
// this.empName是登录人姓名,此操作是在处理当名字大于三个字时,绘画名字的x坐标往前挪一挪,小于三个字时,绘画名字的x坐标往后挪一挪
let startX = this.empName.length>3?262-(this.empName.length-3)*13: 262+(3-this.empName.length)*13;
await this.fillText(ctx, this.empName, startX, 366, {
fontSize: 13,
color: '#333333',
lineHeight: 18,
});
await this.fillText(ctx, '敬上', 306, 366, {
fontSize: 13,
color: '#333333',
lineHeight: 18,
});
// 画完之后的canvas,转成了base64,可以丢到浏览器看绘画结果
console.log('this.canvas.toDataURL()',this.canvas.toDataURL())
},
5. 提升清晰度
丢到浏览器应该是有点糊糊的吧,这里涉及到物理屏幕的一个分辨率,那我们应该怎么办呢,超级超级简单,我们把这个分辨率获取一下,如下
this.dpr = window.devicePixelRatio;
然后在我们指定画布大小,坐标,图片位置大小,文字字体等,可以说是几乎所有的动态参数我们都乘一下就可以了。
6. 完整代码
<template>
<div class="poster">
<canvas
class="poster-canvas"
:style="{ height: imgHeight + 'px' }"
type="2d"
id="posterQuick"
></canvas>
<div class="down_btn" @click="makePoster">下载海报</div>
<div class="lucky-poster" ref="poster" id="poster1">
<img
:src="posterList[postersConf.picIndex].imgurl"
ref="urlInfo"
alt=""
/>
<div class="luckiness">
<div class="name">
{{ name }}
</div>
<div class="text">{{ postersConf.message }}</div>
<div class="company">{{ empName }}<span style="marginLeft: 6px">敬上</span></div>
</div>
</div>
</div>
</template>
<script>
import { Toast } from 'vant';
export default {
data() {
return {
postersConf: {
applicantName: 'hahh',
applicantSexDesc: '男',
empName: 'yanan',
picIndex: 0,
message: 'hello'
},
imgHeight: 0,
dpr: 1,
canvas: null,
posterList: [
{ id: 0, imgurl: require('../images/messageDetail/emptyBirth1.png') },
{ id: 1, imgurl: require('../images/messageDetail/emptyBirth2.png') },
{ id: 2, imgurl: require('../images/messageDetail/emptyBirth3.png') },
],
};
},
created() {
this.canvas = null;
this.dpr = window.devicePixelRatio;
},
mounted() {
},
computed: {
name() {
return `尊敬的${this.postersConf.applicantName}${
this.postersConf.applicantSexDesc == '男' ? '先生' : '女士'
}`;
},
empName() {
return `${this.postersConf.empName}`;
},
},
mounted() {},
methods: {
drawImage(ctx, path, dx, dy, dw, dh) {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = path;
image.onload = () => {
ctx.drawImage(image, dx*this.dpr, dy*this.dpr, dw*this.dpr, dh*this.dpr);
resolve();
};
image.onerror = () => {
reject();
};
});
},
fillText(ctx, text, dx, dy, options = {}) {
let {
color = '#000000',
fontSize = 24,
lineHeight = undefined,
maxWidth = 750,
maxLine = 0,
align = 'left',
bold = false,
lastHidden = false,
indent = 0,
} = options;
lineHeight = lineHeight || fontSize;
ctx.fillStyle = color;
ctx.textAlign = align;
ctx.textBaseline = 'top';
if (bold) {
ctx.font = `bold ${fontSize*this.dpr}px sans-serifl`;
} else {
ctx.font = `${fontSize*this.dpr}px sans-serif`;
}
let words = text.split('');
let line = '';
let lines = [];
for (let i = 0; i < words.length; i++) {
let testLine = line + words[i] + '';
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
if (!lines.length && indent) {
testWidth = testWidth + indent*this.dpr;
}
if (testWidth > maxWidth*this.dpr) {
lines.push(line);
line = words[i] + ' ';
} else {
line = testLine;
}
}
lines.push(line);
for (let j = 0; j < lines.length; j++) {
if (j <= 0 && indent) {
ctx.fillText(lines[j], (dx + indent)*this.dpr, (dy + j * lineHeight)*this.dpr);
} else {
ctx.fillText(lines[j], dx*this.dpr, (dy + j * lineHeight)*this.dpr);
}
}
},
async makePoster() {
let picIndex = this.postersConf.picIndex;
this.canvas = document.getElementById('posterQuick');
let ctx = this.canvas.getContext('2d');
console.log(this.$refs.urlInfo.offsetHeight, 'ctx', ctx);
this.imgHeight = this.$refs.urlInfo.offsetHeight || 812;
this.canvas.width = Math.round(375*this.dpr);
this.canvas.height = Math.round(this.imgHeight*this.dpr);
await this.drawImage(
ctx,
this.posterList[picIndex].imgurl,
0,
0,
375,
this.imgHeight,
);
await this.fillText(ctx, this.name, 43, 218, {
fontSize: 16,
color: '#222222',
bold: true,
});
await this.fillText(ctx, this.postersConf.message, 43, 266, {
fontSize: 13,
color: '#333333',
maxWidth: 290,
lineHeight: 20,
indent: 20,
// maxLine: 5,
});
let startX = this.empName.length>3?262-(this.empName.length-3)*13: 262+(3-this.empName.length)*13;
await this.fillText(ctx, this.empName, startX, 366, {
fontSize: 13,
color: '#333333',
lineHeight: 18,
});
await this.fillText(ctx, '敬上', 306, 366, {
fontSize: 13,
color: '#333333',
lineHeight: 18,
});
let link = this.canvas.toDataURL();
},
},
};
</script>
<style lang="scss" scoped>
.poster-canvas {
width: 375px;
position: absolute;
right: 9999rpx;
top: 9999rpx;
z-index: -10;
}
.down_btn {
position: absolute;
width: 211px;
top: 605px;
left: 82px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: #ff7b28;
border-radius: 22px;
border: 3px solid #ffffff;
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #ffffff;
line-height: 44px;
z-index: 2;
}
.lucky-poster {
font-family: PingFangSC-Medium, PingFang SC;
font-weight: bold;
color: #333333;
position: relative;
img {
display: block;
width: 100%;
}
.luckiness {
position: absolute;
color: rgb(227, 220, 220);
top: 208px;
left: 42px;
right: 42px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 22px;
.name {
font-weight: 600;
font-size: 16px;
color: #222222;
}
.text {
text-indent: 26px;
width: 290px;
height: 105px;
margin-top: 16px;
text-align: justify;
font-style: normal;
line-height: 22px;
}
.company {
line-height: 18px;
text-align: right;
}
}
}
</style>
2312

被折叠的 条评论
为什么被折叠?



