uniapp+canvas实现逐字手写效果

news2024/11/24 0:29:25

在移动端使用 UniApp 进行逐字手写的功能。用户可以在一个 inputCanvas 上书写单个字,然后在特定时间后将这个字添加到 outputCanvas 上,形成一个逐字的手写效果。用户还可以保存整幅图像或者撤销上一个添加的字。

 

  1. 初始化 Canvas

    • 使用 uni.createCanvasContext 创建画布上下文,设置笔触样式和线条属性。
  2. 触摸事件处理

    • handleTouchStart:捕获触摸开始事件,初始化绘图状态。
    • handleTouchMove:捕获触摸移动事件,实时绘制路径。
    • handleTouchEnd:捕获触摸结束事件,启动定时器准备添加字。
  3. 添加字符

    • addChar 方法将 inputCanvas 的内容绘制到 outputCanvas 上,同时保存字符的路径。
  4. 撤销功能

    • undoChar 方法删除上一个字符,并重新绘制 outputCanvas
  5. 保存和上传图像

    • saveImage 方法将 outputCanvas 的内容保存为图片,并调用 upload 方法上传。

完整代码:

<template>
	<view class="container">
		<view class="tip">
			<view class="">
				请您在区域内逐字手写以下文字,全部写完后点击保存!
			</view>
			
			<u-alert style="margin-bottom: 20upx;" :description="ruleForm.sqcn" type = "primary" ></u-alert>
		</view>
		<view class="canvas-container">
			<canvas canvas-id="inputCanvas" class="input-canvas" @touchstart="handleTouchStart"
				@touchmove="handleTouchMove" @touchend="handleTouchEnd"></canvas>
		</view>
		<view class="buttons">
			<u-button text="撤销上一个字" size="normal" type="error" @click="undoChar"></u-button>
			<u-button text="保存" size="normal" type="primary" @click="saveImage"></u-button>
		</view>
		<canvas :style="{ height: outputHeight }" canvas-id="outputCanvas" class="output-canvas"></canvas>
	</view>
</template>

<script>
	import fileService from "@/api/file/fileService.js";
	import knsService from "@/api/kns/knsService"
	export default {
		data() {
			return {
				isDrawing: false,
				startX: 0,
				startY: 0,
				strokes: [],
				canvasWidth: 300,
				canvasHeight: 300,
				charObjects: [],
				timer: null,
				delay: 1000, // 1秒延迟
				fj: '',
				outputHeight: '50px',
				label: '',
				ruleForm: {}
			};
		},
		mounted() {
			this.getData()
			this.initCanvas('inputCanvas');
			this.initCanvas('outputCanvas');
		},
		onLoad(option) {
			this.label = option.label;
		},
		methods: {
			// 获取承诺
			async getData() {
				const res = await knsService.getSettingData();
				this.ruleForm = res[0];
			},
			initCanvas(canvasId) {
				const context = uni.createCanvasContext(canvasId, this);
				context.setStrokeStyle('#000');
				context.setLineWidth(4);
				context.setLineCap('round');
				context.setLineJoin('round');
				context.draw();
			},
			handleTouchStart(e) {
				e.preventDefault(); // 阻止默认滚动行为
				if (this.timer) {
					clearTimeout(this.timer);
					this.timer = null;
				}
				const touch = e.touches[0];
				this.isDrawing = true;
				this.startX = touch.x;
				this.startY = touch.y;
				this.strokes.push({
					x: touch.x,
					y: touch.y
				});
			},
			handleTouchMove(e) {
				e.preventDefault(); // 阻止默认滚动行为
				if (!this.isDrawing) return;
				const touch = e.touches[0];
				const context = uni.createCanvasContext('inputCanvas', this);
				context.moveTo(this.startX, this.startY);
				context.lineTo(touch.x, touch.y);
				context.stroke();
				context.draw(true);
				this.startX = touch.x;
				this.startY = touch.y;
				this.strokes.push({
					x: touch.x,
					y: touch.y
				});
			},
			handleTouchEnd(e) {
				e.preventDefault(); // 阻止默认滚动行为
				this.isDrawing = false;
				this.timer = setTimeout(this.addChar, this.delay);
			},
			addChar() {
				const inputContext = uni.createCanvasContext('inputCanvas', this);
				uni.canvasToTempFilePath({
					canvasId: 'inputCanvas',
					success: (res) => {
						// 保存这个字符的路径
						this.charObjects.push(res.tempFilePath);
						// 清空 inputCanvas 上的内容
						inputContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
						inputContext.draw();
						this.redrawOutputCanvas()
					},
				});
			},
			undoChar() {
				if (this.charObjects.length > 0) {
					this.charObjects.pop();
					this.redrawOutputCanvas();
					if (this.charObjects.length === 0) {
						this.outputHeight = 50; // 如果字符对象为空,则将输出画布高度设置为 50
					}
				}
			},
			redrawOutputCanvas() {
				const context = uni.createCanvasContext('outputCanvas', this);

				const charSize = 50; // 调整字符大小
				const charSpacing = 48; // 调整字符间距
				const maxCharsPerRow = Math.floor(this.canvasWidth / charSpacing); // 每行最大字符数

				// 动态设置高度
				const numRows = Math.ceil(this.charObjects.length / maxCharsPerRow); // 计算行数
				this.outputHeight = `${numRows * charSize}px`; // 动态计算输出画布的高度
				console.log(this.outputHeight, this.charObjects.length, 'outputHeight');

				// 清除画布并设置高度
				context.clearRect(0, 0, this.canvasWidth, this.outputHeight);
				// 绘制字符

				this.charObjects.forEach((charPath, index) => {
					const rowIndex = Math.floor(index / maxCharsPerRow); // 当前字符的行索引
					const colIndex = index % maxCharsPerRow; // 当前字符的列索引
					context.drawImage(charPath, 10 + colIndex * charSpacing, 10 + rowIndex * charSpacing, charSize,
						charSize);
				});
				this.$nextTick(() => {
					// 一次性绘制所有字符
					context.draw();
				})
			},



			saveImage() {
				if (this.charObjects.length === 0) {
					uni.showToast({
						icon: "error",
						title: '请手写文字!'
					})
					return false;
				}

				uni.canvasToTempFilePath({
					canvasId: 'outputCanvas',
					success: (res) => {
						// 保存图片
						console.log(res.tempFilePath, 'res.tempFilePath');
						this.upload(res.tempFilePath);
					},
				});
			},
			upload(img) {
				fileService.upload(img).then((res) => {
					let pages = getCurrentPages()
					let currPage = pages[pages.length - 1]; //当前页面
					let prevPage = pages[pages.length - 2]; //上一个页面

					//修改前一页数据
					if (prevPage.inputForm) {
						prevPage.inputForm[this.label] = res
					} 

					console.log(res, 'res');
					//返回上一页
					uni.navigateBack({
						delta: 1,
					})
				});
			},
		},
	};
</script>

<style scoped lang="scss">
	.container {
		display: flex;
		flex-direction: column;
		align-items: center;
		margin-top: 40upx;

		.canvas-container {
			position: relative;
			width: 600upx;
			height: 600upx;
			.input-canvas {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				border-radius: 10upx;
				border: 4upx dashed #dddee1;
				touch-action: none;
				/* 禁止默认触摸动作 */
			}
		}



		.output-canvas {
			width: 600upx;
			/* 设置高度为原来的一半 */
			border: 2upx solid #dddee1;
			margin-top: 40upx;
		}

		.buttons {
			display: flex;
			justify-content: space-around;
			width: 100%;
			padding: 0upx 50upx;
		}

		button {
			margin: 20upx;
		}

		.tip {
			view:nth-child(1){
				color: #FF6F77;
				font-size: 24upx;
				margin-bottom: 20upx;
			}
		}
	}
</style>

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

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

相关文章

SpringCloud系列(29)--准备使用Hystrix的前期工作,创建服务提供者模块

前言&#xff1a;在上一章节中我们简单的介绍了关于Hystrix的相关知识点&#xff0c;而本节内容则是关于如果去使用Hystrix构建一个支付微服务。 1、创建一个服务提供者模块&#xff0c;命名为cloud-provider-hystrix-payment8001 (1)在父工程下新建模块 (2)选择模块的项目类型…

香橙派华为昇腾CANN架构编译opencv4.9

香橙派华为升腾AI盒子 为啥要编译opencv4.9.0&#xff0c; 因为在4.9.0 中增加了华为昇腾CANN的外接开发库&#xff0c;下图为盒子外观&#xff0c;此次一接到这个盒子&#xff0c;立刻开始开箱操作&#xff0c;首先就是要编译opencv4.9&#xff0c;以前在香橙派3588 的盒子中…

台湾省军事演习路径规划:A*算法在复杂地形中的应用

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

PyTorch 错误 RuntimeError: CUDA error: device-side assert triggered

训练数据的时候出现 RuntimeError:CUDA error:device-side assert triggeredCUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect.For debugging consider passing CUDA_LAUNCH_BLOCKING1.Compile with …

使用Python探究OpenAI API

谁没听说过OpenAI?这家人工智能研究实验室因其著名的产品ChatGPT而改变了世界。它改变了AI实施领域&#xff0c;许多公司现在急于成为下一大热点。 尽管竞争激烈&#xff0c;OpenAI仍然是任何生成式AI业务需求的首选公司&#xff0c;因为它拥有最好的模型和持续的支持。该公司…

齐护K210系列教程(三十四)_视觉PID巡线小车

视觉PID巡线小车 1.前言2.简介3.代码讲解3.1初始化3.2.色块查找3.3色块分析3.3.1 区域13.3.2 区域2 3.4 侦测关键点部分3.4.1正常巡线3.4.2 右转路口 3.4.3十字路口3.4. PID计算 4.完整代码5.小车端程序6.参考程序联系我们 1.前言 本课程主要讲述如何使用AIstart_k210主板完成…

C语言——free() 和 delete() 怎样处理指针?

一、问题 从字⾯上理解&#xff0c;free() 是释放&#xff0c;给予⾃由的意思&#xff1b;⽽ delete() 的含义⽐较直接&#xff0c;是删除的意思。这两个函数的⽬的就是不想再与这个指针有任何关联&#xff0c;那么它们到底怎样处理了不再使⽤的指针呢&#xff1f; 二、解答 其…

❤Element的使用element

❤Element的使用 1、input输入框 禁止自动补全和下拉提示 input 输入框在输入后浏览器会记录用户输入的值&#xff0c;在下次输入的时候会将记录的值提示在输入框的下面&#xff0c;当输入框是密码的时候&#xff0c;这样可以看见上次输入的密码&#xff0c;这样用户体验不好…

融资融券大揭秘:两融入门操作最新指南!

#引言# 随着中国资本市场日新月异的发展进程&#xff0c;融资融券交易已崭露头角&#xff0c;成为投资者们瞩目的焦点。作为一种颇具影响力的投资方式&#xff0c;它正逐渐吸引越来越多的投资者关注。本文旨在为大家深入剖析融资融券交易的核心概念、详尽的操作步骤以及必须留意…

数据清洗(ETL)案例实操

文章目录 数据清洗&#xff08;ETL&#xff09;概述案例需求和分析代码实现和结果分析 数据清洗&#xff08;ETL&#xff09;概述 “ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;Extract&#xff09;、转换&…

医院挂号就诊系统的设计与实现

前端使用Vue.js 后端使用SpiringBoot MyBatis 数据使用MySQL 需要项目和论文加企鹅&#xff1a;2583550535 医院挂号就诊系统的设计与实现_哔哩哔哩_bilibili 随着社会的发展&#xff0c;医疗资源分布不均&#xff0c;患者就诊难、排队时间长等问题日益突出&#xff0c;传统的…

ABAP Json解析案例

ABAP解析返回的JSON 案例 DATA:LTOKEN TYPE STRING.DATA: LL_LINES(10),"行数LL_TABIX(10),"循环标号LL_PECNT TYPE P LENGTH 6 DECIMALS 2, "百分比LL_PECET(6),"百分数LL_TEXT(40)."消息CLEAR: LL_LINES,LL_TABIX,LL_PECNT,LL_PECET,LL_TEXT.* …

文心智能体【焦虑粉碎机】——帮你赶走“坏”情绪

目录&#xff1a; 引言1.登录 文心智能体平台2.创建智能体3.配置智能体&#x1f337; 头像设置&#x1f337; 名称设置&#x1f337; 简介设置&#x1f337;指令设置&#x1f337; 开场白设置&#x1f337; 引导示例设置 4.使用智能体 引言 随着ChatGPT的爆火&#xff0c;人工智…

32.5k star!!替换 postman?【送源码】

在软件开发过程中&#xff0c;API&#xff08;应用程序接口&#xff09;扮演着至关重要的角色。为了确保 API 的可靠性和性能&#xff0c;开发人员需要一种高效的方式来测试和调试它们。这方面的工具&#xff0c;大家经常用到的应该就是 postman 了。不过&#xff0c;今天了不起…

文章解读与仿真程序复现思路——电力系统保护与控制EI\CSCD\北大核心《基于改进粒子滤波的锂离子电池剩余寿命预测 》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

网络编程 一

一、UDP socket api的使用 Java 把系统原生的封装了. 核心的类有两个: 1 -> DatagramSocket 操作系统中,有一类文件,就叫socket文件. socket文件,抽象表示了 " 网卡"这样的硬件设备. 进行网络通信最核心的硬件设备网卡 通过网卡发送数据,就是写…

基于STM32F103和HC-SR04的12路RS485与Modbus通信方案

基于STM32F103和HC-SR04的12路RS485与Modbus通信方案 引言&#xff1a; 随着科技的快速发展&#xff0c;智能汽车技术已成为汽车工业的一个重要发展方向。在智能汽车的众多技术中&#xff0c;环境感知系统扮演着至关重要的角色。超声波雷达作为环境感知系统中的一种低成本、高可…

macOS Monterey 12.7.5 (21H1222) Boot ISO 原版可引导镜像下载

macOS Monterey 12.7.5 (21H1222) Boot ISO 原版可引导镜像下载 5 月 13 日凌晨&#xff0c;macOS Sonoma 14.5 发布&#xff0c;同时带来了 macOS Ventru 13.6.7 和 macOS Monterey 12.7.5 安全更新。 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#x…

蓝海卓越计费管理系统 agent_setstate.php SQL注入漏洞复现

0x01 产品简介 蓝海卓越计费管理系统是一套以实现网络运营为基础,增强全局安全为中心,提高管理效率为目的的网络安全运营管理系统,提供“高安全、可运营、易管理”的运营管理体验,基于标准的RADIUS协议开发,它不仅支持PPPOE和WEB认证计费,还支持802.1X接入控制技术,与其…

BeanFactoryPostProcessor作用

介绍 BeanFactoryPostProcessor是spring中一个很重要的接口&#xff0c;位于spring流程中的获取BeanDefinition之后&#xff0c;实例化之前(点击spring流程)&#xff0c;我们可以实现该接口并注入spring容器中进行拓展&#xff08;对BeanDefinition进行自定义修改&#xff09;…