【uniapp】 合成海报组件

news2025/2/23 23:27:36

之前公司的同事写过一个微信小程序用的 合成海报的组件 非常十分好用 最近的项目是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.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 < 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.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 < 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.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]
    })
  },

文字、图片、矩形、圆角等等情况都考虑了 使用起来非常方便
有需要可以试一试哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1609176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式——访问者模式22

访问者模式能将算法与其所作用的对象隔离开来&#xff0c;分离数据结构与访问数据操作。例如 不同访问者 对不同 文件类型&#xff08;要素&#xff09;的操作权限不同。 设计模式&#xff0c;一定要敲代码理解 元素抽象&#xff08;被访问的元素&#xff09; accept 方法实…

【数据结构】单链表的头节点与尾节点

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;数据结构 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

Appian发布最新版本:通过AI流程自动化推动业务发展

Appian公司于2024年4月16日在弗吉尼亚州麦克莱恩宣布推出Appian平台的最新版本。此版本引入了Process HQ&#xff0c;这是一个集流程挖掘和企业AI于一体的系统&#xff0c;结合了Appian的数据平台。Process HQ为企业运营提供前所未有的可见性&#xff0c;支持数据驱动的决策和流…

nas如何异地共享文件?

nas异地共享文件是一种通过网络实现不同地区电脑与电脑、设备与设备、电脑与设备之间的文件共享的技术。通过nas&#xff08;网络附加存储&#xff09;设备&#xff0c;用户可以在不同地点的电脑或设备之间快速、安全地共享文件和数据。本文将介绍nas异地共享文件的原理以及它在…

宝塔面板使用docker+nginx+gunicorn部署Django项目实战教程

第一步&#xff1a;创建Django项目 使用pip install django安装创建django项目的依赖在电脑某个根目录下执行django-admin startproject app创建一个名为app的Django项目。目录结构如下: ├── app │ ├── init.py │ ├── asgi.py │ ├── settings.py │ ├── url…

SQL注入作业

目录 一、万能密码和二阶注入测试 1.万能密码 2.二阶注入测试 二、联合查询注入测试 1.判断注入点 2.判断当前查询语句的列数 3.查询数据库基本信息 4.查询数据库中的数据 三、报错注入 1. 报错注入函数EXTRATVALUE 2.UPDATEXML 四、盲注测试 1.布尔盲注 判断数据…

【学习】软件压力测试对软件产品的作用

在信息化高速发展的今天&#xff0c;软件产品已经成为各行各业不可或缺的一部分。然而&#xff0c;随着软件功能的日益复杂和用户需求的不断增长&#xff0c;软件产品的稳定性和可靠性问题也愈发凸显。在这样的背景下&#xff0c;软件压力测试作为软件质量保障的重要手段之一&a…

回归预测 | Matlab实现SA-BP模拟退火算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现SA-BP模拟退火算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现SA-BP模拟退火算法优化BP神经网络多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现SA-BP模拟退火算法优化BP神经网络多变量回归预测&#xff0…

盗梦攻击:虚拟现实系统中的沉浸式劫持

虚拟现实&#xff08;VR&#xff09;硬件和软件的最新进展将改变我们与世界和彼此互动的方式&#xff0c;VR头显有可能为用户提供几乎与现实无差别的深度沉浸式体验。它们还可以作为一种跨越遥远距离的方式&#xff0c;通过使用个性化的化身或我们的数字代表&#xff0c;促进社…

旅游陪同翻译难吗, 旅游翻译英译中哪家好?

近来&#xff0c;随着中国旅游业的蓬勃发展&#xff0c;旅游陪同翻译的需求也水涨船高&#xff0c;这些专业的翻译服务者为中外游客搭建起友谊的桥梁&#xff0c;引领他们共同探索中国这片古老而神秘的土地 。那么&#xff0c;旅游陪同翻译英译中难吗&#xff1f;我们如何在众多…

机器学习和深度学习-- 李宏毅(笔记于个人理解)Day 21

Day 21 Self- Attention 选修部分 ​ 学完自适应 再回来看看 Sequence Labling 假如我们现在有一个需要读完全部句子才能解的问题&#xff0c; 那么red window 就需要变得是最大的&#xff08;最长的句子&#xff09;&#xff1b; 其实这里大家有没有想过&#xff0c;这个玩意…

Android Studio历史版本下载地址

https://developer.android.com/studio/archive?hlzh-cn https://blog.csdn.net/crasowas/article/details/130304836

豆瓣影评信息爬取 (爬虫)

代码块&#xff1a; from lxml import etree import requestsheaders{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0 }url_list[] for i in range(0,5):i*20urlsf"https:…

顺序表链表经典算法题

1.链表反转 typedef struct ListNode listnode; struct ListNode* reverseList(struct ListNode* head) {if(head NULL){return head;}listnode* p1 NULL;listnode* p2 head;listnode* p3 head->next;while(p2){p2->next p1;p1 p2;p2 p3;if(p3)p3 p3->next;}…

ubuntu22.04 启用 root登录

1&#xff0c;设置 root密码 普通用户输入如下命令给 root 设置密码 sudo passwd root 根据提示设置密码。 2&#xff0c;允许 root 登录 vim /etc/pam.d/gdm-password 以及 vim /etc/pam.d/gdm-autologin 注释两个文件中如下图所示的代码 3&#xff0c;允许 ssh 方式 ro…

移动端适配之viewport

目录 盒模型&#xff1a;widthcontent&#xff08;padding border&#xff09; class"content-box"内容盒模型&#xff08;W3C盒&#xff09; class"border-box"边框盒模型&#xff08;IE 盒&#xff09; scroll滚动 window浏览器视窗&#xff1a;包括…

IP 和 TCP 抓包分析实验

实验拓扑 实验需求 1、配置IP地址&#xff0c;R1的g0/0口是1.1.1.1/24 &#xff0c;R2的g0/0口是1.1.1.2/24 2、在该链路上开启抓包 3、在R1上ping R2 4、开启wireshark&#xff0c;查看抓取的ping包的内容 5、在R2上开启ftp服务 6、在R1上访问R2的FTP 7、在wireshark查…

【游戏专区】贪吃蛇

1&#xff0c;游戏背景 贪吃蛇&#xff08;Snake&#xff09;是一款经典的电子游戏&#xff0c;最初在1976年由 Gremlin 公司开发。它的游戏背景相对简单&#xff0c;但具有高度的成瘾性。 1. **游戏场景**&#xff1a;通常在一个有界的矩形区域内进行&#xff0c;可以是一个…

电磁炉原理笔记

电磁炉加热原理 【电磁炉工作原理&#xff0c;电涡流感应加热原理】 https://www.bilibili.com/video/BV11M411M7Wt/?share_sourcecopy_web&vd_source44c5c5fe44538189ece80f09460cf625 我是看的这个科普视频&#xff1b; 总结一下就是下图&#xff1a; 线圈的磁场影响…

Spring Boot JNA 实现调用 DLL文件(清晰明了)

概述 项目需要用到 重采样算法&#xff0c;JAVA 没有现成的&#xff0c;只能通过 JNA 调用 C 的 DLL 实现&#xff0c;JNA中&#xff0c;它提供了一个动态的C语言编写的转发器&#xff0c;可以自动实现Java和C的数据类型映射。不再需要编写C动态链接库。 实现需求 根据 一个…