[uni-app] 海报图片分享方案 -canvas绘制

news2024/12/25 23:44:07

文章目录

      • canvas使用记录
        • 先看下实际效果图
        • 绘制流程及思路
          • 1. 绘制头像, 通过`drawImage`来绘制
          • 2.绘制文字部分
        • 具体代码

分享海报图片的方式,以前再RN端采用的是截图方案, 我记得组件好像是

react-native-view-shot

现在要处理uni-app的海报图片分享, 一般也有 html2canvas的相关插件

不过其缺点也有,
比如说遇到bug,有时候没办法修改什么的
手绘canvas虽然麻烦,但是胜在自由灵活

canvas使用记录

先看下实际效果图

请添加图片描述

绘制流程及思路

CanvasContext文档

其实使用下来发现, canvas绘制和iOS原生开发进行UI绘制有很多相似之处, 比如draw的入参 ,都需要x,y坐标,设置width/height等

整个canvas绘制的思路如下

0.绘制整个大矩形背景 : 通过uni.downloadFile下载网络图片来获取tempFilePath, 结合几个坐标和宽高参数, 就可以绘制了

1. 绘制头像, 通过drawImage来绘制

不过这里需要注意的是, 如果要对头像图片进行裁剪,比如圆心之类的
需要用到clip的情况下,
需要提前保存好上下文画布

		// 2.顶部头像
				ctx.save(); // 先保存之前的画布
				ctx.beginPath()
				ctx.arc((200 + 190 / 2) * scaleNum, (60 + 190 / 2) * scaleNum, 190 / 2 * scaleNum, 0,
					Math.PI * 2)
				ctx.clip()
				ctx.drawImage(this.headUrl, 200 * scaleNum, 60 * scaleNum, 190 * scaleNum, 190 *
					scaleNum)

(200: 左边间距)
(190: 圆的直径)
(60: top的间距)

(这里需要注意的事, 圆的圆心坐标, 是相对于x/y坐标的哦 )

2.绘制文字部分

这个没有太大的问题, 不过要注意下 textAlign的具体用法
在这里插入图片描述

具体代码

  1. html部分
			<canvas :style="{width: boxDetail.width +'px', height: boxDetail.height + 'px' }"
				id="sharePic" canvas-id="sharePic"></canvas>

2.js部分

	setCanvasSize() {
				const query = uni.createSelectorQuery().in(this);
				query.select('#content').boundingClientRect(data => {
					this.boxDetail.width = data.width
					this.boxDetail.height = data.height
					console.log(this.boxDetail, data)
					this.$nextTick(() => {
						setTimeout(() => {
							console.log('create and set canvas')
							this.createCanvas()
							// this.asyncAwaitCanvas()
						}, 800)
					})
				}).exec();
			},
			/**
			 * @Description: 创建canvas画布
			 */
			async createCanvas() {
				this.context = uni.createCanvasContext('sharePic', this)
				const ctx = this.context;

				function drawCanvas(url, top) {
					if (url) {
						return new Promise((resolve, reject) => {
							let obj
							obj = { url }
							uni.downloadFile({
								url,
								success: (res) => {
									top ? resolve({ ...res, top }) : resolve({ ...res })
								},
								fail: (err) => {
									reject(err)
								}
							})
						})
					} else {
						return new Promise((resolve, reject) => {
							top ? resolve({ top }) : resolve()
						})
					}
				}
				const scaleNum = this.boxDetail.width / 590
				// 1.背景图
				// - 读背景图资源
				let res = await drawCanvas(
					"https://cdn.froglesson.com/static/cert/share_notes_content_bg.png");
				ctx.save();
				// - 绘制背景图路径/坐标/宽/高
				ctx.drawImage(res.tempFilePath, 0, 0, scaleNum * 590, scaleNum * 864)

				// 2.顶部头像
				ctx.save(); // 先保存之前的画布
				ctx.beginPath()
				ctx.arc((200 + 190 / 2) * scaleNum, (60 + 190 / 2) * scaleNum, 190 / 2 * scaleNum, 0,
					Math.PI * 2)
				ctx.clip()
				ctx.drawImage(this.headUrl, 200 * scaleNum, 60 * scaleNum, 190 * scaleNum, 190 *
					scaleNum)

				// 3.title
				ctx.restore() //恢复一下绘画板
				let title = this.returnName(this.shareData.user_name + ",已学习" + (this.shareData
						.count || 0) +
					"天")
				ctx.setFontSize(32 * scaleNum) //字体大小
				ctx.setFillStyle('#fff') //文字颜色
				ctx.setTextAlign('center') //文本左对其
				ctx.fillText(title, 590 / 2 * scaleNum, (280 + 30) * scaleNum, 530 *
					scaleNum); // marginleft:182rpx;maxWidth:374 最大宽度

				// 4.学习内容
				let content = "今天在「XXXXAPPXXXXX」" + (this.shareData.times > 30 ?
					`${this.shareData.times || 0}分钟` : "学习了")
				content = content + this.studyTitle() + this.studyCount();
				content = "我是中间文字部分,阿爸不不不不不不不不不,啊电话多好多好多好多好等哈,多撒MDJSLSJL"

				// - 字符拆分/计算行数
				let content_top = 0
				let contents = content.trim().split("")
				contents = contents.map((item, index) => {
					if (index && !(index % 20)) {
						item = item + '\n';
					}
					return item
				})
				// 逐行绘制标题文字
				contents.join("").split('\n').forEach((item, index) => {
					ctx.setFontSize(28 * scaleNum) //字体大小
					ctx.setFillStyle('#fff') //文字颜色
					ctx.setTextAlign('center') //文本左对其
					content_top = (365 + 30) * scaleNum + 48 * scaleNum * index //高度计算, 40是行高
					ctx.fillText(item, 590 / 2 * scaleNum, content_top, 530 *
						scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度
				})

				// 5.时间
				let time = this.shareData.day
				ctx.setFontSize(24 * scaleNum) //字体大小
				ctx.setFillStyle('#fff') //文字颜色
				ctx.setTextAlign('center') //文本左对其
				ctx.fillText(time, 590 / 2 * scaleNum, (609 + 30) * scaleNum, 530 *
					scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度

				// 6.icon 
				res = await drawCanvas("https://cdn.froglesson.com/static/cert/index_logo.png")
				ctx.drawImage(res.tempFilePath, 210 * scaleNum, (716 + 30) * scaleNum, 80 * scaleNum,
					80 * scaleNum)

				// 7.qrcode
				res = await drawCanvas(this.qrCode);
				ctx.drawImage(res.tempFilePath, 299 * scaleNum, (716 + 30) * scaleNum, 80 * scaleNum,
					80 * scaleNum)

				// 8.底部title
				let appName = "XXXXAPPNAMEXXXXXX"
				ctx.setFontSize(24 * scaleNum) //字体大小
				ctx.setFillStyle('#fff') //文字颜色
				ctx.setTextAlign('center') //文本左对其
				ctx.fillText(appName, 590 / 2 * scaleNum, (801 + 50) * scaleNum, 530 *
					scaleNum); // marginleft:182rpx;maxWidth:148 最大宽度




				// 完成绘制 - 执行保存
				ctx.draw()
				console.log('绘制完毕')
				uni.setStorageSync('openSaveShareImage', true)
				this.saveOver()
			},

			saveOver() {
				console.log("绘制----------end")
				setTimeout(() => {
					uni.canvasToTempFilePath({
						canvasId: 'sharePic',
						destWidth: this.boxDetail.width * 2, //展示图片尺寸=画布尺寸1*像素比2
						destHeight: this.boxDetail.height * 2,
						quality: 1,
						fileType: 'png',
						success: (result) => {
							console.log(result.tempFilePath)
							uni.saveImageToPhotosAlbum({
								filePath: result.tempFilePath,
								success: function() {
									uni.hideToast()
									uni.showToast({
										icon: 'none',
										title: "图片已下载至【相册】,请打开【相册】查看", // res.tempFilePath
									});
								},
								fail: function() {
									uni.hideToast()

									uni.showToast({
										icon: 'none',
										title: "保存失败", // res.tempFilePath
									});
								}
							})
						},
					}, this);
				}, 1000)
			},

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

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

相关文章

二维码怎么做调查问卷?二维码统计数据的技巧

小伙伴们一定发现&#xff0c;现在用来收集用户数据时&#xff0c;通过扫码填表的方式在很多场景中都被应用&#xff0c;这种方式有效的提升制作者获取用户数据的速度&#xff0c;而且对于填表者而言也更加的便捷。那么用二维码生成器&#xff08;免费在线二维码生成器-二维码在…

Docker网络功能

基本网络功能 Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。使用docker network子命令来管理Docker网络。 外部访问容器可通过端口映射实现&#xff0c;启动容器时使用-p参数指定映射关系。-p可多次使用来绑定多个端口。使用docker port命令查看当前映射的端…

VR全景拍摄发展如何?在各行业应用中有优势吗?

现如今&#xff0c;虚拟现实技术正在以惊人的速度改变着我们的生活&#xff0c;而VR全景拍摄作为一种创新的拍摄方式&#xff0c;可以为大家带来全新的视觉体验。通过VR全景拍摄&#xff0c;可以将平面画面变得更加逼真、更具沉浸感&#xff0c;让人仿佛置身于真实场景之中。 近…

蚂蚁发布金融大模型:两大应用产品支小宝2.0、支小助将在完成备案后

9月8日&#xff0c;在上海举办的外滩大会上&#xff0c;蚂蚁集团正式发布金融大模型。据了解&#xff0c;蚂蚁金融大 模型基于蚂蚁自研基础大模型&#xff0c;针对金融产业深度定制&#xff0c;底层算力集群达到万卡规模。该大 模型聚焦真实的金融场景需求&#xff0c;在“认知…

恭贺弘博创新2023下半年软考(中/高级)认证课程顺利举行

为迎接2023年下半年软考考试&#xff0c;弘博创新于2023年9月2日举行了精品的软考中/高级认证课程&#xff0c;线下线上学员都积极参与学习。 在课程开始之前&#xff0c;弘博创新的老师为学员们提供了详细的学习资料和准备建议&#xff0c;以确保学员们在课程中能够跟上老师的…

如何排查网站及APP数据泄露的源头

近年来数据泄露安全事件频发&#xff0c;在今年的hw网络安全攻防演练中&#xff0c;获取敏感信息、数据泄露等漏洞的得分也越来越高&#xff0c;我们SINE安全近十年来成功的帮助了许多客户&#xff0c;查找到了数据泄露的原因&#xff0c;在这里向大家分享我们的经验与心得&…

程序员面试逻辑题

红白帽子推理 答案&#xff1a; 这个题有点像数学归纳法&#xff0c;就是假设有 A A A和 B B B两个人是黑色的帽子&#xff0c;这样的话第一次开灯&#xff0c; A A A看到 B B B是黑色的&#xff0c;其他人都是白色的&#xff0c;那么 A A A会觉得 B B B是那个黑色的&#xff0…

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献 1 题目 1.1 问题背景 多波束测深系统是利用声波在水中的传播特性来测量水体深度的技术&#xff0c;是在单波束测深的基础上发展起来的&#xff0c;该系统在与航迹垂直的平面内一次能发射出数十个乃至上百个…

从gles,vulkan到metal(二)-- 同步和内存

无论以任何形式本篇文章内容&#xff0c;请注明来自leonnwei的csdn blog 在第一章节中我们讨论了图形API的基本数据结构和图形指令的提交机制&#xff0c;在指令的生成&#xff0c;提交&#xff0c;执行过程中&#xff0c;在复杂而又高度并行的GPU管线中&#xff0c;必然存在着…

安全模型中的4个P

引言&#xff1a;在安全模型中&#xff0c;经常会碰到PDR,PPDR&#xff0c;IPDRR&#xff0c;CARTA-PPDR等模型&#xff0c;其中的P&#xff0c;是predicet&#xff1f;是prevent&#xff1f;还是protect&#xff1f;还是policy呢&#xff1f; 一、4P字典意思解释 1、predict&…

【C++基础】3. 数据类型

文章目录 【 1. 数据类型分类 】1.1 字符型1.2 整型1.3 浮点型1.4 布尔型1.5 无类型1.6 枚举类型1.7 其他类型1.8 类型占用大小输出 【 2. typedef 类型声明 】 使用编程语言进行编程时&#xff0c;我们需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置&am…

iOS创建Category类别(扩展类)

我要为UIButton扩展其他系统提供以外的方法&#xff0c;这就要用到扩展类了 创建步骤如下图&#xff1a; 步骤一&#xff1a;New File 步骤二&#xff1a;选中Objective-c File 步骤三&#xff1a;File名称随便起(这里写了AaaBtn)&#xff0c;继承Class为UIButton 步骤四&…

4. xaml Button按钮

1.运行图片 2.源码 <Grid><!--BorderBrush="BlueViolet" 边框颜色--><!--BorderThickness="2" 边框线的粗细--><

软件测试适合零基础学么

零基础学习软件测试不失为一个好的选择&#xff0c;虽然IT行业里对小白最友好的非软件测试莫属了&#xff0c;但是也要看你个人在学习软件测试这件事上面花费了多少的时间和努力了~ 每年毕业季&#xff0c;IT行业依然是比较热门且收入是最高的行业。对于应届毕业生来说想要进入…

【python自动化】七月PytestAutoApi开源框架学习笔记(二)

执行流程 注&#xff1a;请先阅读作者的README.md文档https://gitee.com/yu_xiao_qi/pytest-auto-api2/blob/master/README.md 本节内容目录如下&#xff1a; 文章目录 执行流程目录结构参数配置入口文件-run.pypytest.ini test_case初始化数据缓存解析yaml测试数据 测试用例…

学习Bootstrap 5的第八天

目录 加载器 彩色加载器 实例 闪烁加载器 实例 加载器大小 实例 加载器按钮 实例 分页 分页的基本结构 实例 活动状态 实例 禁用状态 实例 分页大小 实例 分页对齐 实例 面包屑&#xff08;Breadcrumbs&#xff09; 实例 加载器 彩色加载器 在 Bootstr…

微信个人号如何实现自动回复,秒回客户消息?

企业在开展私域运营沉淀用户的过程中&#xff0c;免不了要与来自各个渠道的用户打交道。在客户关注公众号、视频号或者发送QQ消息后&#xff0c;通过设置欢迎语&#xff0c;主动推送内容。 那么微信是不是也可以通过设置像它们一样可以自动欢迎语呢&#xff1f; 其实是可以的。…

谷歌为什么不收录你的网站?

答案是&#xff1a;因为你的文章质量太差&#xff0c;建议使用GPC爬虫池促收录。 谷歌作为全球最大的搜索引擎&#xff0c;对于许多网站主、营销人员和SEO专家来说&#xff0c;确保自己的网站被谷歌收录是至关重要的。 但有时&#xff0c;即使我们做了很多努力&#xff0c;我…

Mybatis中 collection 和 association 标签 的区别

<collection> 和 <association> 是 MyBatis 中用于定义映射关系的标签&#xff0c;它们的区别如下&#xff1a; 目标对象类型&#xff1a; <collection> 用于表示集合属性&#xff0c;即一个属性对应多个关联对象。<association> 用于表示关联属性&…