1、页面代码
<template>
<view class="demo">
<canvas :style="{ width: canvasW + 'px', height: canvasH + 'px' }" canvas-id="myCanvas" id="myCanvas"></canvas>
</view>
</template>
<script>
import {
drawTextVertical
} from './canvas';
export default {
components: {},
data() {
return {
canvasW: 0, // 画布宽
canvasH: 0, // 画布高
SystemInfo: {}, // 设备信息
goodsImg: {}, // 主图
ratio: 0, // 比率
}
},
async onLoad() {
// 获取设备信息,主要获取宽度,赋值给canvasW 也就是宽度:100%
this.SystemInfo = await this.getSystemInfo();
// 获取主图,APP端会返回图片的本地路径(H5端只能返回原路径)
this.goodsImg = await this.getImageInfo(
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fd1.lanrentuku.com%2Fupload%2Fimg%2Fpy5s1c%2F202011022342%2F5fa028e595edf.jpg&refer=http%3A%2F%2Fd1.lanrentuku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1672207134&t=97d23e38c9db282abb73632ad9953367'
);
console.log('--goodsImg--', this.goodsImg)
// this.canvasW = this.SystemInfo.windowWidth/ this.SystemInfo.pixelRatio; // 画布宽度
// this.canvasH = (this.goodsImg.height + 50)/this.SystemInfo.pixelRatio; // 画布高度 = 主图高度 + 文字图片的间距(大概50)
this.canvasW = this.SystemInfo.windowWidth; // 画布宽度
this.canvasH = this.goodsImg.height; // 画布高度
if (this.goodsImg.errMsg == 'getImageInfo:ok' && this.SystemInfo
.errMsg == 'getSystemInfo:ok') {
console.log('ok')
uni.showToast({
icon: 'loading',
mask: true,
duration: 1000,
title: '海报绘制中',
});
setTimeout(() => {
let ctx = uni.createCanvasContext('myCanvas', this);
// 填充背景色,白色
ctx.setFillStyle('red'); // 默认白色
// ctx.fillRect(0, 0, this.canvasW, this.canvasH) // fillRect(x,y,宽度,高度)
// 绘制主图
ctx.drawImage(this.goodsImg.path, 0, 0, this.canvasW, this
.canvasW) // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度)
/**
* 感谢:李文龙大夫
* 妙手回春,华佗再世
* 龙吴路赠送
*/
ctx.font = `normal 400 ${(18 * this.ratio).toFixed(0)}px my-font3`;
// ctx.setFontSize(16) // 字号
ctx.setFillStyle('#ecdc40') // 颜色
// ctx.fillText('¥' + this.price, 10, this.canvasW + 75); // (文字,x,y)
drawTextVertical(ctx, '感谢', (this.canvasW - this.getDistance(30)), this.getDistance(15),
22 * this.ratio)
ctx.fillText(':', (this.canvasW - this.getDistance(30)), this.getDistance(26));
drawTextVertical(ctx, '李文龙大夫', (this.canvasW - this.getDistance(30)), this.getDistance(33),
22)
ctx.setFontSize(32 * this.ratio) // 字号
drawTextVertical(ctx, '华佗再世', (this.canvasW - this.getDistance(40)), this.getDistance(28),
45)
drawTextVertical(ctx, '妙手回春', (this.canvasW - this.getDistance(55)), this.getDistance(28),
45)
ctx.font = `normal 400 ${(16 * this.ratio).toFixed(0)}px my-font1`;
// ctx.setFontSize(16)
ctx.setFillStyle('#ecdc40')
drawTextVertical(ctx, '龙吴路赠送', this.getDistance(35), this.getDistance(56), 13, 'bottom')
let dateText = this.converToDate('2022-11-28')
// drawTextVertical(ctx, dateText, this.getDistance(29), this.getDistance(42), 13)
drawTextVertical(ctx, dateText, this.getDistance(29), this.getDistance(78), 13, 'bottom')
ctx.draw(true, (ret) => { // draw方法 把以上内容画到 canvas 中。
// console.log(ret)
uni.showToast({
icon: 'success',
mask: true,
title: '绘制完成',
});
const that = this;
uni.canvasToTempFilePath({ // 保存canvas为图片
x: 0,
y: 0,
canvasId: 'myCanvas',
fileType: 'png',
quality: 1, //图片质量
success(res) {
// console.log(res.tempFilePath, 'canvas生成图片地址11')
},
complete(res) {
// console.log(res.tempFilePath, 'canvas生成图片地址11')
// that.downloadImg(res.tempFilePath)
// 预览
// uni.previewImage({
// current: 0, //预览图片的下标
// urls: [res
// .tempFilePath] //预览图片的地址,必须要数组形式,如果不是数组形式就转换成数组形式就可以
// })
// 在H5平台下,tempFilePath 为 base64, // 图片提示跨域 H5保存base64失败,APP端正常输出临时路径
// console.log(res)
},
})
});
}, 500)
} else {
console.log('err')
}
},
methods: {
// 获取图片信息
getImageInfo(image) {
return new Promise((req, rej) => {
uni.getImageInfo({
src: image,
success: function(res) {
// console.log('获取图片信息',res)
req(res)
},
});
})
},
getDistance(num, minNum = 0) {
// num = num / 100;
num = num / 100;
let windowWidth = this.SystemInfo.windowWidth // 可使用窗口宽度
let data = windowWidth * num;
if (windowWidth > data > minNum) {
return data
} else {
return minNum
}
},
// 获取设备信息
getSystemInfo() {
return new Promise((req, rej) => {
uni.getSystemInfo({
success: (res) => {
// console.log('获取设备信息',res)
// console.log('获取设备信息',res.windowWidth,res.windowHeight)
this.ratio = res.windowWidth / 375;
console.log('--this.ratio--', this.ratio)
req(res)
}
});
})
},
converToDate(date) {
let today = new Date();
let chinese = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
let y = today.getFullYear().toString();
let m = (today.getMonth() + 1).toString();
let d = today.getDate().toString();
let result = "";
for (var i = 0; i < y.length; i++) {
result += chinese[y.charAt(i)];
}
result += "年";
if (m.length == 2) {
if (m.charAt(0) == "1") {
result += ("十" + chinese[m.charAt(1)] + "月");
}
} else {
result += (chinese[m.charAt(0)] + "月");
}
if (d.length == 2) {
result += (chinese[d.charAt(0)] + "十" + chinese[d.charAt(1)] + "日");
} else {
result += (chinese[d.charAt(0)] + "日");
}
return result;
},
downloadImg(url) {
// const that = this;
// // #ifdef H5
// var $code_img = document.getElementById('code_img');
// console.log($code_img.src)
// // #endif
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
console.log(res.tempFilePath);
this.saveImg(res.tempFilePath)
}
}
});
},
saveImg(url) {
var oA = document.createElement("a");
oA.download = ''; // 设置下载的文件名,默认是'下载'
oA.href = url;
document.body.appendChild(oA);
oA.click();
oA.remove(); // 下载之后把创建的元素删除
},
},
}
</script>
<style>
.my-font1 {
font-family: my-font1
}
.my-font2 {
font-family: my-font2
}
.my-font3 {
font-family: my-font3
}
</style>
2、垂直绘制文本 (drawTextVertical)方法
function drawTextVertical(context, text, x, y, interval, direction) {
direction = direction || 'top'
let arrText = text.split('');
let arrWidth = arrText.map(function(letter) {
return interval || 20;
// 这里为了找到那个空格的 bug 做了许多努力,不过似乎是白费力了
// const metrics = context.measureText(letter);
// console.log(metrics);
// const width = metrics.width;
// return width;
});
let align = context.textAlign;
let baseline = context.textBaseline;
if (align == 'left') {
x = x + Math.max.apply(null, arrWidth) / 2;
} else if (align == 'right') {
x = x - Math.max.apply(null, arrWidth) / 2;
}
if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
y = y - arrWidth[0] / 2;
} else if (baseline == 'top' || baseline == 'hanging') {
y = y + arrWidth[0] / 2;
}
context.textAlign = 'center';
context.textBaseline = 'middle';
// 开始逐字绘制
arrText.forEach(function(letter, index) {
// // 确定下一个字符的纵坐标位置
// var letterWidth = arrWidth[index];
// 是否需要旋转判断
let code = letter.charCodeAt(0);
if (code <= 256) {
context.translate(x, y);
// 英文字符,旋转90°
context.rotate(90 * Math.PI / 180);
context.translate(-x, -y);
} else if (index > 0 && text.charCodeAt(index - 1) < 256) {
// y修正
y = y + arrWidth[index - 1] / 2;
}
context.fillText(letter, x, y);
// 旋转坐标系还原成初始态
context.setTransform(1, 0, 0, 1, 0, 0);
// 确定下一个字符的纵坐标位置
let letterWidth = arrWidth[index];
console.log('====', direction)
if (direction == 'top') {
y = y + letterWidth;
} else {
y = y - letterWidth;
}
});
// 水平垂直对齐方式还原
context.textAlign = align;
context.textBaseline = baseline;
}
module.exports = {
drawTextVertical: drawTextVertical
}
3、效果图
4、其他npm包
vue-canvas-poster-fixleft
Vue Canvas Poster 一个通过 css 属性画 canvas 图片的轻量级的 vue 组件。
@ubbcou/vue-canvas-poster
vue-canvas-poster 的 vue3版本。
vue-canvas-poster-yufan
一个基于vue+canvas通过类CSS数据生成canvas图片的组件。