之前公司的同事写过一个微信小程序用的 合成海报的组件 非常十分好用 最近的项目是uni的 把组件改造一下也可以用 记录一下
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.width canDom.height = data.height let ctx = canDom.getContext('2d') let d = data.txt let font = ` ${d.weight || 'italic'} ${d.size || 30}px ${d.fontFamily || '微软雅黑'}` // console.log('font', font); ctx.font = font ctx.textAlign = d.align let 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.border ctx.strokeStyle = d.color ctx.stroke() } else { ctx.fillStyle = d.color ctx.fill() } ctx.restore() }, // 绘制文字 drawText(d, ctx) { let font = ` ${d.weight || 'italic'} ${d.size || 30}px ${d.fontFamily || '微软雅黑'}` // console.log('font', font); ctx.font = font ctx.textAlign = d.align let val = d.value let isSlice = false if (d.maxWidth) { let tw = ctx.measureText(val) // console.log('tw', tw); while (tw.width > d.maxWidth) { isSlice = true let 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.color ctx.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.y let leftWidth = d.x var lineWidth = 0; var lastSubStrIndex = 0; //每次开始截取的字符串的索引 for (let i = 0; i 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.color ctx.fillText(str.substring(lastSubStrIndex, i + 1), leftWidth, initHeight); } } }, // 绘制矩形 drawRect(d, ctx) { ctx.save() if (d.border) { ctx.strokeStyle = d.color ctx.lineWidth = d.border ctx.strokeRect(d.x, d.y, d.w, d.h) } else { ctx.fillStyle = d.color ctx.fillRect(d.x, d.y, d.w, d.h) } ctx.restore() }, loadFont(d) { if (!d.font) return let 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.width canDom.height = data.height return new Promise(async r => { if (data.loading) uni.showLoading({ title: '合成中' }) let ctx = canDom.getContext('2d') for (let i = 0; i { if (data.loading) uni.hideLoading() r(file.tempFilePath) } }, this) }) // return new Promise((r) => { // uni.nextTick(() => { // }) // }) } } } ._mycanvas { position: absolute; right: -1000000000px; top: -100000000px; /* top: 0; left: 0; width: 750rpx; background-color: pink; */ }
这次用的组件是放在分包里了 顺便记录一下 分包调用组件
目录结构是这样子的 想在index.vue页面调用
import Draw from '@/threeSubManage/components/draw-2d/draw-2d.vue'; // 引入draw组件 export default { components: { Draw // 注册draw组件 }, }
重点来了 合成图片的函数是这样子
async goDraw() { let cdn = 'http://192.168.1.1/cdn/' let width = 500, height = 400 // || this.data.Url.imgUrl let 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] }) },
文字、图片、矩形、圆角等等情况都考虑了 使用起来非常方便
有需要可以试一试哦~