当前位置: 首页 > news >正文

canvas实现手写功能

1.从接口获取手写内容,处理成由单个字组成的数组(包括符号)
2.合成所有图的时候,会闪现outputCanvas合成的图,注意隐藏
3.可以进行多个手写内容切换
4.基于uniapp的

<template><view class="content"><!-- 头部 --><view class="navBarBox"><!-- 头部导航 --><u-navbar height="120rpx"><view class="u-nav-slot" slot="left"><view class="flex alignCenterClass flexBetween" @click="_close"><view class="leftBox"><img src="static/imgs/leftIcon.png" alt="" /></view></view></view><view class="centerBox" slot="center"><view class="title"> 批注声明 </view><u--texttype="error"text="请您在区域内逐字手写以下文字,全部写完后点击保存!"size="30rpx"align="center"></u--text></view><view class="u-nav-slot flex" slot="right"><viewclass="btn-box signerBox"v-if="isComplete"@click="_submitDraw"><text> 完成 </text><u-icon name="checkmark" color="#fff" size="26"></u-icon></view></view></u-navbar></view><view class="content-model"><view class="model-left"><viewv-for="(item, index) in docNoteList"class="note-item":key="item.code"@click="_checkNotes(item, 'click')"><viewclass="note-btn btn-box":class="curNode.code == item.code ? 'actice-node' : ''"><text>批注{{ index + 1 }}</text><u-iconv-if="curNode.code == item.code"name="edit-pen"color="#fff"size="28"></u-icon></view><u-iconv-if="item.isDone"name="checkmark-circle"color="#087e6a"size="26"></u-icon></view></view><view class="container"><view class="notes-list"><scroll-viewscroll-y="true"style="height: 600rpx"class="scroll-view_w"enable-flex="true"scroll-with-animation="true"><view class="note-box"><viewv-for="item in curNode.notesList"@click="_checkItem(item)":key="item.index"class="notes-item":class="activeItem.index == item.index ? 'active' : ''"><view class="note-label">{{ item.label }}</view><!-- 展示写好的字 --><view class="note-img"><img v-if="item.imgSrc" :src="item.imgSrc" alt="" /></view></view></view></scroll-view></view><view class="main-model"><view class="show-canvas"><!-- canvas背景字 --><view class="bg-text"><text>{{ activeItem.label }}</text></view><!-- 当前需要手写的字的canvas --><view class="canvas-container"><canvascanvas-id="inputCanvas"class="input-canvas"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"></canvas></view></view></view><!-- 将单个字的图片合并章所用的canvas --><view class="show-result"><canvas:style="{ height: outputHeight, width: outputWidth }"canvas-id="outputCanvas"class="output-canvas"></canvas></view></view></view></view>
</template><script>
import { pathToBase64 } from "../../utils/image-tools/index.js";
import { addNoteImg, getDocData } from "@/utils/api.js";
export default {components: {},data() {return {show: true,isDrawing: false,startX: 0,startY: 0,strokes: [],charObjects: [],timer: null,delay: 500, // 写完后的延迟fj: "",outputHeight: "100px",outputWidth: "100px",tempFilePath: "", //当前显示的图片activeItem: {}, //当前的批注文字document: "",docNoteList: [], //所有批注列表curNode: {notesList: [],}, //当前批注isComplete: false, //当前文档所有批注书否全部写完,控制完成按钮的显示tempPathObj: {},showImg: "", //批注合成图};},computed: {},methods: {// 返回上一页_close() {uni.redirectTo({url: "/pages/index/fileEdit?documentId=" + this.document.documentId,});},_checkItem(val) {this.activeItem = { ...val };this.tempFilePath = val.imgSrc || "";},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.setStrokeStyle("#000");context.setLineWidth(15);context.setLineJoin('round');context.setLineCap('round');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;// 写完后延迟,清空Canvas,获取手写图this.timer = setTimeout(this.addChar, this.delay);},addChar() {const inputContext = uni.createCanvasContext("inputCanvas", this);uni.canvasToTempFilePath({canvasId: "inputCanvas",success: (res) => {// 清空 inputCanvas 上的内容inputContext.clearRect(0, 0, 700, 700);inputContext.draw();this._pathToBase64(res.tempFilePath, "show");},});},//批注合成处理_drawImage(notesList, imgCode) {this.showImg = "";let outputContext = "";outputContext = uni.createCanvasContext("outputCanvas", this);const charSize = 40; // 调整字符大小const maxCharsPerRow = 20; // 每行最大字符数// 动态设置高度const numRows = Math.ceil(notesList.length / maxCharsPerRow); // 计算行数this.outputHeight = `${numRows * charSize}px`; // 动态计算输出画布的高度this.outputWidth = `${maxCharsPerRow * charSize}px`; // 动态计算输出画布的宽度// 绘制字符let rowSpacing = "";let colSpacing = "";notesList.forEach((item, index) => {const rowIndex = Math.floor(index / maxCharsPerRow); // 当前字符的行索引const colIndex = index % maxCharsPerRow; // 当前字符的列索引rowSpacing = rowIndex * charSize;colSpacing = colIndex * charSize;outputContext.drawImage(item.tempFilePath,colSpacing,rowSpacing,charSize,charSize);});setTimeout(() => {outputContext.draw(false, () => {uni.canvasToTempFilePath({canvasId: "outputCanvas",success: (result) => {uni.compressImage({//压缩图片src: result.tempFilePath,success: (res) => {pathToBase64(res.tempFilePath).then((base64) => {//赋值const _nodeImg = this.filterBase64(base64);this.tempPathObj[imgCode] = _nodeImg;console.log("tempPathObj", this.tempPathObj);this.showImg = _nodeImg;// 清空 outputContext 上的内容outputContext.clearRect(0,0,colSpacing + charSize * charSize,rowSpacing + numRows * charSize);outputContext.draw();}).catch((error) => {console.error(error);});},});},});});}, 500);},_checkNotePass() {let isComplete = true; //是否全部批注都写完,默认都写完了const _curNode = this.curNode;this.docNoteList.map((item) => {if (item.code == _curNode.code) {item.isDone = _curNode.notesList.every((item) => {return item.imgSrc != "";});if (item.isDone) {this._drawImage(_curNode.notesList, _curNode.imgCode);}}if (!item.isDone) {//如有未完成的isComplete = false;}});this.isComplete = isComplete;},_pathToBase64(val, code) {uni.compressImage({//压缩图片src: val,success: (res) => {pathToBase64(res.tempFilePath).then((base64) => {const _notesList = this.curNode.notesList;const signImg = this.filterBase64(base64);// 手写处理_notesList.map((item) => {if (item.index == this.activeItem.index) {item.imgSrc = signImg;item.tempFilePath = res.tempFilePath;}});this.tempFilePath = signImg;this._checkNotePass();// 自动轮下一个if (this.activeItem.index < _notesList.length - 1) {this._checkItem(_notesList[this.activeItem.index + 1]);}}).catch((error) => {console.error(error);});},});},// 过滤base64太长有换行字符方法filterBase64(codeImages) {return codeImages.replace(/[\r\n]/g, "");},// 保存更新async _submitDraw() {let documentData = {...this.document.documentData,};for (const key in this.tempPathObj) {documentData[key] = this.tempPathObj[key];}const query = {documentId: this.document.documentId,documentData: JSON.stringify(documentData),};await addNoteImg(query).then(({ data: res }) => {if (res.code == 0) {uni.showToast({icon: "success",title: "保存成功",});uni.redirectTo({url: "/pages/index/fileEdit?documentId=" + this.document.documentId,});}});},// 获取文书详情async _getDocData(documentId) {await getDocData({ documentId }).then(({ data: res }) => {const _documentData = JSON.parse(res.data.documentData);this.document = {documentId: res.data.documentId,documentData: _documentData,};this._checkDocData(_documentData);});},// 处理批注,形成列表_checkDocData(data) {let docNote = [];for (const key in data) {const element = data[key];//根据实际需求处理这一步if (key.includes("note_text")) {let notesList = [];// 处理批注内容const _text = element.split("");for (let i = 0; i < _text.length; i++) {const ele = _text[i];notesList.push({label: ele,imgSrc: "",index: i,tempFilePath: "",code: key,});}const _key = key.split("note_text")[1] || "";const _imgCode = "note_img" + _key;docNote.push({code: key,imgCode: _imgCode,label: element,nodeImg: "", //最终合成的图片isDone: false, //当前批注是否已写完notesList, //所有批注问字});this.$set(this.tempPathObj, _imgCode, "");}}console.log(docNote, 142545);this.docNoteList = docNote;this.curNode = { ...docNote[0] };this._checkItem(this.curNode.notesList[0]);},// 处理当前批注数据_checkNotes(value) {this.curNode = { ...value };this.showImg = this.tempPathObj[value.imgCode];this._checkItem(this.curNode.notesList[0]);},},beforeDestroy() {},onLoad(option) {this._getDocData(option.documentId);},created() {},
};
</script><style scoped lang="scss">
.content {height: 100vh;
}
.content-model {width: 100%;margin-top: 80rpx;display: flex;justify-content: space-around;align-items: flex-start;.model-left {width: 12%;.note-item {display: flex;justify-content: space-between;align-items: center;}.note-btn {margin-bottom: 30rpx;background-color: #ee7c36;}.actice-node {background-color: $mainColor;}}
}
.centerBox {text-align: center;
}
.signerBox {background-color: $mainColor;
}
.btn-box {display: flex;justify-content: space-around;align-items: center;color: #fff;padding: 12rpx 50rpx;border-radius: 40rpx;font-size: 40rpx;
}
.navBarBox {.leftBox {width: 40rpx;height: 40rpx;margin-right: 40rpx;margin-top: -20rpx;img {width: 100%;height: 100%;}}
}.container {display: flex;flex-direction: column;align-items: center;width: 87%;.show-result {position: fixed;bottom: -200prx;width: 40%;z-index: 1;background-color: #ee7c36;}.notes-list {width: 100%;display: flex;flex-direction: column;justify-content: center;flex-wrap: wrap;// background-color: rgba(223, 220, 219,0.2);background-color: #fff;margin-bottom: 25rpx;position: relative;z-index: 999;.scroll-view_w {width: 100% !important;}.note-box {width: 100%;display: flex;flex-direction: row;justify-content: center;flex-wrap: wrap;}.note-label,.note-img {width: 120rpx;height: 120rpx;line-height: 120rpx;text-align: center;margin-right: 5rpx;margin-bottom: 3rpx;border: 2rpx solid #999;background-color: #fff;font-size: 100rpx;img {width: 100%;height: 100%;}}.active {.note-label,.note-img {border: 2rpx solid rgba(212, 21, 53, 0.9);}}}
}
.main-model {display: flex;justify-content: center;width: 100%;.show-img,.show-canvas {position: relative;width: 700rpx;height: 700rpx;border: 8rpx dashed #dddee1;}.show-img img {z-index: 999;}.show-canvas .bg-text {position: absolute;top: -30rpx;left: 0;width: 100%;height: 100%;font-size: 300px;line-height: 700rpx;text-align: center;color: rgba(153, 153, 153, 0.1);}.canvas-container {width: 100%;height: 100%;.input-canvas {position: absolute;top: 0;left: 0;width: 100%;height: 100%;border-radius: 10rpx;touch-action: none;// background-color: #ee7c36;/* 禁止默认触摸动作 */}}
}
.temp-img {width: 300rpx;height: 100rpx;img {width: 100%;height: 100%;}border: 2rpx solid #dddee1;
}
</style>

效果图:
在这里插入图片描述

相关文章:

canvas实现手写功能

1.从接口获取手写内容&#xff0c;处理成由单个字组成的数组&#xff08;包括符号&#xff09; 2.合成所有图的时候&#xff0c;会闪现outputCanvas合成的图&#xff0c;注意隐藏 3.可以进行多个手写内容切换 4.基于uniapp的 <template><view class"content&quo…...

Python知识点:基于Python技术,如何使用TensorFlow进行目标检测

开篇&#xff0c;先说一个好消息&#xff0c;截止到2025年1月1日前&#xff0c;翻到文末找到我&#xff0c;赠送定制版的开题报告和任务书&#xff0c;先到先得&#xff01;过期不候&#xff01; 使用TensorFlow进行目标检测的完整指南 目标检测是计算机视觉领域中的一项重要任…...

初始爬虫13(js逆向)

为了解决网页端的动态加载&#xff0c;加密设置等&#xff0c;所以需要js逆向操作。 JavaScript逆向可以分为三大部分&#xff1a;寻找入口&#xff0c;调试分析和模拟执行。 1.chrome在爬虫中的作用 1.1preserve log的使用 默认情况下&#xff0c;页面发生跳转之后&#xf…...

前端发送了请求头的参数,经debug发现后端请求对象请求头中没有该参数

debug测试&#xff0c;发现前端发来请求头中确实没有找到添加的请求头参数&#xff0c;但是 Network 中却显示请求头中有该参数信息。 原因是RequestHeaders中设置的请求参数含有下划线&#xff0c;NGINX将静默地丢弃带有下划线的HTTP标头&#xff0c;这样做是为了防止在将头映…...

雷池社区版如何使用静态资源的方式建立站点

介绍&#xff1a; SafeLine&#xff0c;中文名 “雷池”&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击。 雷池通过过滤和监控 Web 应用与互联网之间的 HTTP 流量来保护 Web 服务。可以保护 Web 服务免受 SQL 注入、X…...

车载电源OBC+DC/DC

文章目录 1. 车载DC/DC应用场景2. PFC2.1 简介2.2 专业名词2.3 常见拓扑结构2.3.1 传统桥式PFC2.3.2 普通无桥型PFC2.3.3 双Boost无桥PFC2.3.4 图腾柱PFC2.3.5 参考资料 2.4 功率因数2.4.1 简介2.4.2 计算 3. DC/DC3.1 Boost升压电路3.1.1 简介3.1.2 电路框图3.1.3 工作原理3.1…...

【朝花夕拾】免费个人网页搭建:免费托管、CDN加速、个人域名、现代化网页模板一网打尽

现代化网页设计的免费宝藏&#xff1a;GitHub PagesCodePenCloudflareUS.KG 前言 在当今数字化时代&#xff0c;个人和企业越来越重视在线形象的建立。GitHub Pages 提供了一个免费且便捷的平台&#xff0c;允许用户托管静态网站。然而&#xff0c;GitHub Pages 默认的域名可…...

Spring Boot知识管理系统:用户体验设计

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…...

《数字信号处理》学习08-围线积分法(留数法)计算z 逆变换

目录 一&#xff0c;z逆变换相关概念 二&#xff0c;留数定理相关概念 三&#xff0c;习题 一&#xff0c;z逆变换相关概念 接下来开始学习z变换的反变换-z逆变换&#xff08;z反变化&#xff09;。 由象函数 求它的原序列 的过程就称为 逆变换。即 。 求z逆变换…...

vue3中的computed属性

模板界面&#xff1a; <template><div class"person"><h2>姓&#xff1a; <input type"text" v-model"person.firstName" /></h2><h2>名&#xff1a; <input type"text" v-model"person…...

C++学习笔记之vector容器

天上月&#xff0c;人间月&#xff0c;负笈求学肩上月&#xff0c;登高凭栏眼中月&#xff0c;竹篮打水碎又圆。 山间风&#xff0c;水边风&#xff0c;御剑远游脚下风&#xff0c;圣贤书斋翻书风&#xff0c;风吹浮萍又相逢。 STL(Standard Template Library,标准模板库 ) 从…...

LeNet-5(论文复现)

LeNet-5&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 LeNet-5&#xff08;论文复现&#xff09;概述LeNet-5网络架构介绍训练过程测试过程使用方式说明 概述 LeNet是最早的卷积神经网络之一。1998年&#xff0c;Yann LeCun第一次将LeN…...

基于SpringBoot+Vue+Uniapp汽车保养系统小程序的设计与实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而…...

【问题实战】Jmeter中jtl格式转换图片后如何分开展示各个性能指标?

【问题实战】Jmeter中jtl格式转换图片后如何分开展示各个性能指标&#xff1f; 遇到的问题解决方法查看修改效果 遇到的问题 JMeter测试计划中只设置了一个性能监控器jpgc - PerfMon Metrics Collector&#xff1b;在这个监控器中设置几个性能监控指标&#xff0c;比如CPU、Di…...

解决 MySQL 连接数过多导致的 SQLNonTransientConnectionException 问题

这里写目录标题 解决 MySQL 连接数过多导致的 SQLNonTransientConnectionException 问题1. 概述2. 问题描述异常日志的关键部分&#xff1a; 3. 原因分析3.1. MySQL 连接数配置3.2. 连接池配置问题3.3. 代码中未正确关闭连接3.4. 高并发导致连接需求激增 4. 解决方案4.1. 增加 …...

猫头虎分享:什么是 ChatGPT 4o Canvas?

猫头虎是谁&#xff1f; 大家好&#xff0c;我是 猫头虎&#xff0c;猫头虎技术团队创始人&#xff0c;也被大家称为猫哥。我目前是COC北京城市开发者社区主理人、COC西安城市开发者社区主理人&#xff0c;以及云原生开发者社区主理人&#xff0c;在多个技术领域如云原生、前端…...

qiankun 主项目和子项目都是 vue2,部署在同一台服务器上,nginx 配置

1、主项目配置 1.1 micro.vue 组件 <template><div id"container-sub-app"></div> </template><script> import { loadMicroApp } from qiankun; import actions from /utils/actions.js;export default {name: microApp,mixins: [ac…...

深入浅出MongoDB(七)

深入浅出MongoDB&#xff08;七&#xff09; 文章目录 深入浅出MongoDB&#xff08;七&#xff09;查询优化创建索引以支持读取操作查询选择性覆盖查询 分析性能使用数据库分析器评估对数据库的操作使用db.currentOp()评估mongod操作使用explain评估查询性能 优化查询性能创建索…...

【华为】配置NAT访问互联网

1.AR1&#xff1a; int g0/0/0 ip ad 64.1.1.2 255.255.255.0 int g0/0/1 ip ad 110.242.68.1 255.255.255.02.AR2: (1)配置端口ip: int g0/0/1 ip ad 10.3.1.2 255.255.255.0 int g0/0/0 ip ad 64.1.1.1 255.255.255.0(2)配置默认路由&#xff1a; ip route-static 0.0.0.0 0.…...

Spring Boot项目使用多线程执行定时任务

我在一个Spring Boot项目中&#xff0c;采用定时器执行一些操作&#xff0c;比如10秒就发送一次数据。这些操作有2个&#xff0c;如下所示。我就想&#xff0c;虽然这两个操作各自指定了时间频率&#xff0c;但如果其中一个操作非常耗时&#xff0c;会不会影响其他操作呢&#…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...