【uniapp】 合成海报组件

发布于:2024-04-20 ⋅ 阅读:(20) ⋅ 点赞:(0)

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

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