uniapp+canvas实现逐字手写效果

在移动端使用 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.rhkb.cn/news/330949.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用FFmpeg推流实现在B站24小时点歌直播

使用FFmpeg推流实现在B站24小时点歌直播 本文首发于个人博客 安装FFmpeg centos7 https://www.myfreax.com/how-to-install-ffmpeg-on-centos-7/ https://linuxize.com/post/how-to-install-ffmpeg-on-centos-7/ 使用FFmpeg在B站直播 https://zhuanlan.zhihu.com/p/2395…

超级初始网络

目录 一、网络发展史 1、独立模式 2、局域网 LAN&#xff08;Local Area Network&#xff09; 3、广域网 WAN (Wide Area Network) 二、网络通信基础 1、IP地址&#xff1a;用于定位主机的网络地址 2、端口号&#xff1a;用于定位主机中的进程 3、网络协议 4、五元组 …

基于卷积神经网络的交通标志识别(pytorch,opencv,yolov5)

文章目录 数据集介绍&#xff1a;resnet18模型代码加载数据集&#xff08;Dataset与Dataloader&#xff09;模型训练训练准确率及损失函数&#xff1a;resnet18交通标志分类源码yolov5检测与识别&#xff08;交通标志&#xff09; 本文共包含两部分&#xff0c; 第一部分是用re…

LeetCode 279 —— 完全平方数

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此图利用动态规划进行求解&#xff0c;首先&#xff0c;我们求出小于 n n n 的所有完全平方数&#xff0c;存放在数组 squareNums 中。 定义 dp[n] 为和为 n n n 的完全平方数的最小数量&#xff0c;那么有状态…

mysql中text,longtext,mediumtext区别

文章目录 一.概览二、字节限制不同三、I/O 不同四、行迁移不同 一.概览 在 MySQL 中&#xff0c;text、mediumtext 和 longtext 都是用来存储大量文本数据的数据类型。 TEXT&#xff1a;TEXT 数据类型可以用来存储最大长度为 65,535(2^16-1)个字符的文本数据。如果存储的数据…

【服务器】使用mobaXterm远程连接服务器

目录 1、安装mobaXterm2、使用mobaXterm3、程序后台保持运行状态 1、安装mobaXterm 下载地址&#xff1a;https://mobaxterm.mobatek.net/download.html 下载免费版 分为蓝色便携版&#xff08;下载后可直接使用&#xff09;和绿色安装版&#xff08;需要正常安装后使用&…

【老王最佳实践-6】Spring 如何给静态变量注入值

有些时候&#xff0c;我们可能需要给静态变量注入 spring bean&#xff0c;尝试过使用 Autowired 给静态变量做注入的同学应该都能发现注入是失败的。 Autowired 给静态变量注入bean 失败的原因 spring 底层已经限制了&#xff0c;不能给静态属性注入值&#xff1a; 如果我…

Go语言(Golang)的开发框架

在Go语言&#xff08;Golang&#xff09;的开发中&#xff0c;有多种开发框架可供选择&#xff0c;它们各自具有不同的特点和优势。以下是一些流行的Go语言开发框架&#xff0c;选择Go语言的开发框架时&#xff0c;需要考虑项目需求、团队熟悉度、社区支持、框架性能和可维护性…

docker- 购建服务镜像并启动

文章目录 前言docker- 购建服务镜像并启动1. 前期准备2. 构建镜像3. 运行容器4. 验证 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实…

基于微信小程序的校园捐赠系统的设计与实现

校园捐赠系统是一种便捷的平台&#xff0c;为校园内的各种慈善活动提供支持和便利。通过该系统&#xff0c;学生、教职员工和校友可以方便地进行捐赠&#xff0c;并了解到相关的项目信息和捐助情况。本文将介绍一个基于Java后端和MySQL数据库的校园捐赠系统的设计与实现。 技术…

PGP软件安装文件加密解密签名实践记录

文章目录 环境说明PGP软件安装PGP软件汉化AB电脑新建密钥并互换密钥对称密钥并互换密钥 文件加密和解密A电脑加密B电脑解密 文件签名A电脑签名文件B电脑校验文件修改文件内容校验失败修改文件名称正常校验 环境说明 使用VM虚拟两个win11,进行操作演示 PGP软件安装 PGP软件下…

【Andoird开发】android获取蓝牙权限,搜索蓝牙设备MAC

<!-- Android 12以下才需要定位权限&#xff0c; Android 9以下官方建议申请ACCESS_COARSE_LOCATION --><uses-permission android:name"android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name"android.permission.ACCES…

通过域名接口申请免费的ssl多域名证书

来此加密已顺利接入阿里云的域名接口&#xff0c;用户只需一键调用&#xff0c;便可轻松完成域名验证&#xff0c;从而更高效地申请证书。接下来&#xff0c;让我们详细解读一下整个操作过程。 来此加密官网 免费申请SSL证书 免费SSL多域名证书&#xff0c;泛域名证书。 首先&a…

【游戏引擎】Unity脚本基础 开启游戏开发之旅

持续更新。。。。。。。。。。。。。。。 【游戏引擎】Unity脚本基础 Unity脚本基础C#语言简介C#基础 Unity脚本基础创建和附加脚本MonoBehaviour生命周期生命周期方法 示例脚本 Unity特有的API常用Unity API 实践示例&#xff1a;制作一个简单的移动脚本步骤1&#xff1a;创建…

水泥超低排平台哪家好?

随着环保政策的加强和绿色发展理念的深入人心&#xff0c;水泥行业的超低排放改造已成为行业发展的新趋势。选择一个合适的水泥超低排平台对于确保改造效果和实现企业的可持续发展至关重要。朗观视觉小编将从多个角度出发&#xff0c;为您提供一份综合评估与选择攻略&#xff0…

Flask-SQLAlchemy的使用【二】

目录 一.查询 1.1查询语句的格式 1.2查询过滤器 1.3查询执行器 1.4具体例子 1.4.1查询有多少个用户 1.4.2查询第一个用户 1.4.3查询id为4的用户 1.4.4查询id为4title为4的记录 1.4.5查询id为4或者title为4的记录 1.4.6查询id为[1,3,5,7,9]的记录 1.4.7查询所有记录&a…

无人机助力光伏项目测绘建模

随着全球对可再生能源需求的不断增长&#xff0c;光伏项目作为其中的重要一环&#xff0c;其建设规模和速度都在不断提高。在这一背景下&#xff0c;如何高效、准确地完成光伏项目的测绘与建模工作&#xff0c;成为了行业发展的重要课题。近年来&#xff0c;无人机技术的快速发…

汇聚荣科技有限公司优点有哪些?

在当今快速发展的科技时代&#xff0c;企业之间的竞争愈发激烈。作为一家专注于科技创新与研发的公司&#xff0c;汇聚荣科技有限公司凭借其卓越的技术实力和创新能力&#xff0c;在业界树立了良好的口碑。那么&#xff0c;汇聚荣科技有限公司究竟有哪些优点呢?接下来&#xf…

WPF中MVVM架构学习笔记

MVVM架构是一种基于数据驱动的软件开发架构&#xff0c;它将数据模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和视图模型&#xff08;ViewModel&#xff09;三者进行分离&#xff0c;使得开发者可以更加专注于各自领域的开发。其中&#xff0c;Model负…

Add object from object library 从对象库中添加内置器件

Add object from object library 从对象库中添加内置器件 正文正文 对于 Lumerical,有些时候我们在使用中,可能需要从 Object library 中添加器件,通常我们的做法是手动添加。如下图所示,我们添加一个 Directional Coupler 到我们的工程文件中: 但是这种操作方式不够智能…