之前公司的同事写过一个微信小程序用的 合成海报的组件 非常十分好用 最近的项目是uni的 把组件改造一下也可以用 记录一下
<template><view><canvas type="2d" class="_mycanvas" id="my-canvas" canvas-id="my-canvas" :style="canCss" /></view>
</template><script>export default {name: "draw-2d",data() {return {};},methods: {getCanvas(canvasId) {return new Promise((r) => {this.createSelectorQuery().select(canvasId).fields({node: true}).exec(res => {// console.log(res);r(res[0].node)})})},// 给定一串文字样式 获取他在canvas的宽度async getTxtWidth(data) {// this.setData({// canCss: `width:${data.width}px;height:${data.height}px;`// })this.canCss=`width:${data.width}px;height:${data.height}px;`let canvasId = '#my-canvas'await this.loadFont(data)let canDom = await this.getCanvas(canvasId)canDom.width = data.widthcanDom.height = data.heightlet ctx = canDom.getContext('2d')let d = data.txtlet font = ` ${d.weight || 'italic'} ${d.size || 30}px ${d.fontFamily || '微软雅黑'}`// console.log('font', font);ctx.font = fontctx.textAlign = d.alignlet tw = ctx.measureText(d.value)return tw},// 绘制图片drawImage(d, ctx, cav) {let img = cav.createImage()return new Promise((r) => {img.onerror = () => {console.log('下载失败');r()}img.onload = () => {ctx.save();if (d.isCir) {ctx.beginPath(); //开始绘制ctx.arc(d.w / 2 + d.x, d.h / 2 + d.y, d.w / 2, 0, Math.PI * 2, true);ctx.clip(); //画好了圆 剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因}if (d.radius) {// 需要裁剪圆角矩形图片ctx.save();ctx.beginPath();ctx.moveTo(d.x + d.radius, d.y);ctx.arcTo(d.x + d.w, d.y, d.x + d.w, d.y + d.h, d.radius);ctx.arcTo(d.x + d.w, d.y + d.h, d.x, d.y + d.h, d.radius);ctx.arcTo(d.x, d.y + d.h, d.x, d.y, d.radius);ctx.arcTo(d.x, d.y, d.x + d.w, d.y, d.radius);ctx.strokeStyle = 'transparent'ctx.closePath()ctx.stroke();ctx.clip();}ctx.drawImage(img, d.x, d.y, d.w, d.h)// if (d.border) {// ctx.save()// ctx.strokeStyle = d.border.color// ctx.lineWidth = d.border.size// ctx.strokeRect(d.x, d.y, d.w, d.h)// ctx.restore()// }ctx.restore()r()}img.src = d.src})},// 绘制圆形drawCir(d, ctx) {ctx.save()ctx.beginPath();ctx.arc(d.x, d.y, d.size, d.size, 0 * Math.PI / 180, 360 * Math.PI / 180)if (d.border) {ctx.lineWidth = d.borderctx.strokeStyle = d.colorctx.stroke()} else {ctx.fillStyle = d.colorctx.fill()}ctx.restore()},// 绘制文字drawText(d, ctx) {let font = ` ${d.weight || 'italic'} ${d.size || 30}px ${d.fontFamily || '微软雅黑'}`// console.log('font', font);ctx.font = fontctx.textAlign = d.alignlet val = d.valuelet isSlice = falseif (d.maxWidth) {let tw = ctx.measureText(val)// console.log('tw', tw);while (tw.width > d.maxWidth) {isSlice = truelet len = val.pointLen()val = val.sliceByPoint(0, len - 1)tw = ctx.measureText(val + '...')// console.log('tw', tw);}}if (isSlice) {// console.log('裁剪过了 需要拼接');val = val + '...'}// console.log('val', val);ctx.fillStyle = d.colorctx.fillText(val, d.x, d.y)// 字体描边// ctx.strokeStyle = "blue";// ctx.font = " italic 40px 宋体";// ctx.strokeText("你好", d.x, d.y);},// 绘制需要自动换行的文字drawText1(d, ctx) {let font = ` ${d.weight || 'normal'} ${d.size || 16}px ${d.fontFamily || 'Arial'}`;ctx.font = font;ctx.textAlign = d.align || 'left';ctx.textBaseline = 'top'; // 确保文本从顶部开始绘制 let str = d.value;let maxWidth = d.maxWidth // 默认设置为无限大,以确保没有限制 let linesize = d.linesize || 100; // 默认行高为字体大小 let initHeight = d.ylet leftWidth = d.xvar lineWidth = 0;var lastSubStrIndex = 0; //每次开始截取的字符串的索引for (let i = 0; i < str.length; i++) {lineWidth += ctx.measureText(str[i]).width;if (lineWidth > maxWidth) {console.log('str.substring(lastSubStrIndex, i)', str.substring(lastSubStrIndex, i),lastSubStrIndex, i);ctx.fillText(str.substring(lastSubStrIndex, i), leftWidth, initHeight); //绘制截取部分initHeight += linesize; //字体的高度lineWidth = 0;lastSubStrIndex = i;i--// titleHeight += 30;}if (i == str.length - 1) { //绘制剩余部分ctx.fillStyle = d.colorctx.fillText(str.substring(lastSubStrIndex, i + 1), leftWidth, initHeight);}}},// 绘制矩形drawRect(d, ctx) {ctx.save()if (d.border) {ctx.strokeStyle = d.colorctx.lineWidth = d.borderctx.strokeRect(d.x, d.y, d.w, d.h)} else {ctx.fillStyle = d.colorctx.fillRect(d.x, d.y, d.w, d.h)}ctx.restore()},loadFont(d) {if (!d.font) returnlet r1 = []d.font.map(v => {let p = new Promise((r) => {uni.loadFontFace({family: v.name,scopes: ['native'],source: d.cdn + v.src,global: true,complete: r})})r1.push(p)})return Promise.all(r1)},async goDraw(data) {console.log('async goDraw(data)');// this.setData({// canCss: `width:${data.width}px;height:${data.height}px;`// })this.canCss=`width:${data.width}px;height:${data.height}px;`let canvasId = '#my-canvas'await this.loadFont(data)let canDom = await this.getCanvas(canvasId)canDom.width = data.widthcanDom.height = data.heightreturn new Promise(async r => {if (data.loading) uni.showLoading({title: '合成中'})let ctx = canDom.getContext('2d')for (let i = 0; i < data.data.length; i++) {let v = data.data[i]if (v.type == 'image') {if (v.isNeedCdn) {v.src = data.cdn + v.src}await this.drawImage(v, ctx, canDom)}if (v.type == 'text') {this.drawText(v, ctx)}if (v.type == 'text1') {this.drawText1(v, ctx)}if (v.type == 'rect') {this.drawRect(v, ctx)}if (v.type == 'cir') {this.drawCir(v, ctx)}}uni.canvasToTempFilePath({canvasId: canvasId,canvas: canDom,x: 0,y: 0,width: data.width,height: data.height,destWidth: data.width * data.scale,destHeight: data.height * data.scale,success: (file) => {if (data.loading) uni.hideLoading()r(file.tempFilePath)}}, this)})// return new Promise((r) => {// uni.nextTick(() => {// })// })}}}
</script><style lang="scss">._mycanvas {position: absolute;right: -1000000000px;top: -100000000px;/* top: 0;left: 0;width: 750rpx;background-color: pink; */}
</style>
这次用的组件是放在分包里了 顺便记录一下 分包调用组件
目录结构是这样子的 想在index.vue页面调用
<template><Draw id='draw' ref='draw'></Draw>
</template><script>import Draw from '@/threeSubManage/components/draw-2d/draw-2d.vue'; // 引入draw组件export default {components: {Draw // 注册draw组件 },}
</script>
重点来了 合成图片的函数是这样子
async goDraw() {let cdn = 'http://192.168.1.1/cdn/'let width = 500, height = 400// || this.data.Url.imgUrllet font = [{ name: 'egg1', src: 'egg1.ttf' }]let data = [// 普通图片 需要拼接cdn 如果是头像或者后台返回的图片链接 isNeedCdn就不用填 默认false{ type: 'image', x: 0, y: 0, w: width, h: height, src: 'share.jpg', isNeedCdn: true },// isCir 是否是圆形图片 一般用作头像{ type: 'image', x: width - 120, y: 100, isCir: 1, w: 80, h: 80, src: 'mall/image.png', isNeedCdn: true },// 圆角图片 deg 就是被裁的px{type: 'image', x: width / 2 - 40 / 2, y: 20, radius: 10,// border: { size: 6, color: 'red' },w: 40, h: 40, src: 'event/share-h2.png', isNeedCdn: true},// 文字 size 是文件大小 color 颜色 fontfamily 字体{type: 'text', value: '居中的字阿阿阿阿', x: width / 2, y: height / 2,maxWidth: 300, //最大宽度size: 34, weight: '100',align: 'center', color: 'red', fontFamily: 'egg1'},{type: 'text', value: '靠左的文字阿阿阿阿', x: 10, y: 100,maxWidth: 300, //最大宽度size: 34, weight: '100',align: 'left', color: 'red', fontFamily: 'egg1'},{type: 'text', value: '靠右的文字阿阿阿阿', x: width - 10, y: 140,maxWidth: 300, //最大宽度size: 34, weight: '100',align: 'right', color: 'red', fontFamily: 'egg1'},//需要换行的文字,type传text1{type: 'text1', value: this.currentIns, x: 92 / 1.5, y: (713) / 1.5,maxWidth: 540 / 1.5, //最大宽度linesize: 64 / 1.5,size: 32 / 1.5, weight: '100',align: 'left', color: 'white', fontFamily: 'egg1'},// 矩形框 填充色为蓝色 没有border 默认就是背景填充{ type: 'rect', x: 10, y: 10, w: 50, h: 50, color: 'blue', border: 0 },// 圆形框 填充色color{ type: 'cir', x: width / 2, y: height - 80, border: 0, size: 40, color: 'red' },]let d = { width, height, loading: true, font, scale: 2, cdn, data }let drawDom = this.selectComponent('#draw')// let r = await drawDom.goDraw(d)let r = await this.$refs.draw.goDraw(d);uni.previewImage({urls: [r]})},
文字、图片、矩形、圆角等等情况都考虑了 使用起来非常方便
有需要可以试一试哦~