vue使用原生canvas绘制海报

前段时间,公司项目要求绘制商品的分享海报,当时尝试使用了html2canvas,但是后来发现,它只有在Safari浏览器上面效果比较理想,但是在Android手机上面,各种移位,总之就是不能生成完整的图。虽然后面有尝试过在绘制过程中加一个延时,但是效果还是不太好,最后还是决定使用原生canvas来制作了。过程虽然麻烦些,但是最终的效果还是不错的。

先上一张效果图
在这里插入图片描述

下面是我整理的绘制过程

前期准备

 <div class="hidden" style="display: none;">
      <canvas id="mycanvas" :width="w" :height="h" />
      <img class="goodsImg" src="" alt="" crossOrigin="*">
 </div>
 <img class="postImg" src alt>

这里,canvas模块,我是用display: none 将其隐藏,因为只需要获取到canvas生成的base64格式的图片就可以了,而原始的canvas内容不展示也不影响。
之所以这样处理,canvas的宽度高度,要比生成的图片的宽高大一些,这样生成的图片会清晰很多。

data() {
	return {
		w: 650,
		h: 1200
	}
}
.postImg {
	width: 570px;
}

注意:canvas的宽高最好用 width 和 height 属性,以免变形。
写好布局,接下来还有比较重要的一步:

对于绘制图片的处理
这里分为两种情况: 一种是本地图片绘制,一种是网络图片绘制;

其实两种绘制差别上不是很大,只是网络图片需要解决图片跨域的问题。
解决方法也是有的,需要在 oss 上面去配置一下允许跨域。这个需要找公司负责这方面人员去帮你配置,当然如果你自己也可以配置的话,那也再好不过了,具体的配置方法网上有很多,可以自己去搜。

这里我用的是网络图片,所以 src 的属性是空的,后面会在获取到数据后填充上的。

如果是绘制本地图片,需要把上面的 html 代码稍作修改

<div class="hidden" style="display: none;">
      <canvas id="mycanvas" :width="w" :height="h" />
      <img src="本地图片地址" alt="" />
 </div>
 <img class="postImg" src alt>

vue中如果要把本地图片绘制出来,一定要直接把 img标签 写在 html里。 通过 let img = new Image(),img.src = 本地图片路径,这种方式,没有办法将图片绘制出来;这样做的结果就是,不管你用了什么办法,图片就是出不来。。。。
现在准备工作基本上就完了,接下来就是比较重要的绘制过程了。

绘制

data(){
	return {
		w: 650,
		h: 1200,
		info: null
	}
},
methods: {
	draw() {
		// 这里可以添加一个loading
		
		let qrImg = new Image()  // 创建二维码图片对象
		qrImg.src = this.info.url  // 二维码 base64 链接码

		let goodsImg = ''
		goodsImg =  document.querySelector('.goodsImg')
		goodsImg.src = this.info.goodsImg  // 网络图片地址

		// 开始绘制
		goodsImg.onload = () => {
			let canvas = document.getElementById('mycanvas')
			let ctx = canvas.getContext('2d', {
                 antialias: true  // 是画质更清晰  其他配置可以参考文档
            })
			
			// 填充背景色
			ctx.fillStyle = '#ffffff'
			ctx.fillRect(0, 0, 650, 1200)
			
			// 绘制价格
			ctx.fillStyle = '#E85700'  // 设置字体颜色
            ctx.font = '24px PingFang SC'  // 字体、字体大小
            ctx.fillText('¥', 50, 720)  // 文字内容和位置  相当于绝对定位left 和top值  定位参照位置是canvas区域左上角
            ctx.font = '36px PingFang SC'
            ctx.fillText(`${this.info.sharePrice1}${this.info.sharePrice2}`, 75, 720)  // 价格涉及到小数点,所以对价格做了处理,拼在一起是一个完整的价格
            ctx.fillStyle = 'rgba(153,153,153,1)'   // 切换颜色 绘制原价
            ctx.font = '24px PingFangSC-Regular,PingFang SC'
            ctx.fillText(`原价:¥${this.info.afterPrice1}${this.info.afterPrice2}`, 220, 720)
			
			// 绘制标题
			ctx.fillStyle = 'rgba(0, 0, 0, 1)'
			ctx.font = '36px PingFangSC-Regular,PingFang SC'
			/*  标题分了两行绘制,因为标题长度是随机的,只绘制一行,
				效果不好;
				所以这里写了一个方法,可以截取固定字节的字符串
				标题长度超过28字节,截取前28字节的字符串,在第
				一行绘制;
				然后拿到剩余的字符串second,截取固定26字节字符串
				strSecond,对比两者的的长度是否一致,或者两者的
				内容是否完全一样。如果一样,second== strSecond,
				可以直接绘制 second; 如果 second > strSecond,
				可以在 strSecond 后面 + '...' ,再绘制,达到溢出
				省略的效果。
			*/
			let first = this.autoAddEllipsis(this.info.title, 28)
			ctx.fillText(first, 50, 780)
			let second = this.info.title.substr(first.length)
			let strSecond = this.autoAddEllipsis(this.info.title.substr(first.length), 26)

			if (second == strSecond) {
				ctx.fillText(second, 50, 830)
			} else {
				ctx.fillText(strSecond + '...', 50, 830)
			}
			
			// 绘制提示语
			ctx.fillStyle = 'rgba(153,153,153,1)'
			ctx.font = '24px PingFangSC-Regular,PingFang SC'
			ctx.fillText('长按或扫描查看', 232, 1110)

			ctx.drawImage(qrImg, 211, 872, 209, 209)  // 绘制二维码
			ctx.drawImage(proImg, 0, 0, 650, 650) // 绘制商品图片
			let Url = document.getElementById('mycanvas').toDataURL('image/png')  // 转base64
			document.querySelector('.postImg').src = Url  // 展示图片
			
			// 哈哈,这里又写了一遍,因为出现二维码没有绘制出来的情况
			qrImg.onload = () => {
				ctx.drawImage(qrImg, 211, 872, 209, 209)
				ctx.drawImage(proImg, 0, 0, 650, 650)
				let Url = document.getElementById('mycanvas').toDataURL('image/png')
				document.querySelector('.postImg').src = Url
				
				// 绘制完成 清除loading   什么loading,我也不知道
			}
		}
	}
}

这样这个海报就绘制完成了,剩余的就是怎么处理这个海报了。
然后那个每次都截取固定字符串的方法,简单说一下,因为我也是从别人那里搜刮来的,不是自己的成果,不好意思放上来。就说一下大概思路:

autoAddEllipsis 方法 接收两个参数:
str 需要截取的字符串,
length 需要截取的字节长度

然后它的核心是一句话: Unicode编码大于255说明占两个字节(至于如何将字符转为Unicode编码,可以自行查文档)
str的每个字符都需要转码计算它的字节数,所以需要对 str 进行遍历,判断每个字符的字节数 是一个还是两个,然后进行计数count,直到count 超过 length未知。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值