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

uniapp vue3 canvas实现手写签名

在这里插入图片描述

userSign.vue

<template><view class="signature"><view class="btn-box" v-if="orientation === 'abeam'"><button @click="clearClick">重签</button><button @click="finish">完成签名</button></view><canvas id="canvas" canvas-id="canvas" :disable-scroll="true" @touchmove="move" @touchstart="start" @error="error"@touchend="touchend" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas><view class="btn-box" v-if="orientation === 'portrait'"><button type="warn" @click="clearClick">重签</button><button type="primary" @click="finish">完成签名</button></view></view>
</template><script setup>import {ref,onMounted,getCurrentInstance} from 'vue'const props = defineProps({// 分享orientation: {type: String,default: "portrait", // 竖向},width: {type: Number,default: 0,},height: {type: Number,default: 0,},// 字体粗细lineWidth: {type: Number,default: 3,},// 字体颜色strokeStyle: {type: String,default: "black",},});const emit = defineEmits(["finish", "clear"]);const instance = getCurrentInstance().proxy;const ctx = ref("");const pr = ref(0);const canvasWidth = ref("");const canvasHeight = ref("");const points = ref([]);onMounted(() => {getSystemInfo();createCanvas();});// 触摸开始const start = (e) => {points.value.push({X: e.touches[0].x,Y: e.touches[0].y});ctx.value.beginPath();};// 开始移动const move = (e) => {points.value.push({X: e.touches[0].x,Y: e.touches[0].y}); //存点draw(); //绘制路径};const touchend = () => {points.value = [];};const draw = () => {const point1 = points.value[0];const point2 = points.value[1];points.value.shift();ctx.value.moveTo(point1.X, point1.Y);ctx.value.lineTo(point2.X, point2.Y);ctx.value.stroke();ctx.value.draw(true);};const createCanvas = () => {ctx.value = uni.createCanvasContext("canvas", instance, {willReadFrequently: true,});ctx.value.lineGap = "round";ctx.value.lineJoin = "round";ctx.value.lineWidth = props.lineWidth; // 字体粗细ctx.value.strokeStyle = props.strokeStyle; // 字体颜色};const canvasW = ref(300);const canvasH = ref(300);// 获取系统信息const getSystemInfo = () => {uni.getSystemInfo({success: (res) => {pr.value = res.pixelRatio;if (props.orientation == "portrait") {if (props.width > res.windowWidth || props.width == 0) {canvasWidth.value = res.windowWidth;} else {canvasWidth.value = props.width;}if (props.height > res.windowHeight - 70 || props.height == 0) {canvasHeight.value = res.windowHeight - 70;} else {canvasHeight.value = props.height;}} else if (props.orientation == "abeam") {if (props.width > res.windowWidth - 70 || props.width == 0) {canvasWidth.value = res.windowWidth - 70;} else {canvasWidth.value = props.width;}if (props.height > res.windowHeight || props.height == 0) {canvasHeight.value = res.windowHeight;} else {canvasHeight.value = props.height;}}// 我写死的canvasHeight.value = 300;const rate = canvasHeight.value / canvasWidth.value;canvasW.value = 300;canvasH.value = 300 / rate;},});};// canvas 的errorconst error = (e) => {console.log("画出错了" + e);};// 重签const clearClick = () => {ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);ctx.value.draw(true);emit("clear");};// 点击完成签名const finish = () => {uni.canvasToTempFilePath({canvasId: "canvas",success: (res) => {const path = res.tempFilePath;emit("finish", path);},});};// 如果想要base64格式const finish = () => {uni.canvasToTempFilePath({canvasId: "canvas",x: 0,y: 0,width: canvasWidth.value,height: canvasHeight.value,destWidth: canvasWidth.value * pr.value, // 乘以像素比保证高清destHeight: canvasHeight.value * pr.value,fileType: 'png',quality: 1, // 最高质量success: (res) => {// 通过文件系统读取 base64const base64 = uni.getFileSystemManager().readFileSync(res.tempFilePath, 'base64')console.log('base64=', `data:image/png;base64,${base64}`);emit('finish', `data:image/png;base64,${base64}`)},fail: (err) => {console.error('生成签名失败:', err)}}, instance);};defineExpose({clearClick,});
</script><style scoped lang="scss">canvas {background-color: white;}.signature {width: 100%;height: 100%;display: flex;flex-wrap: wrap;align-items: flex-end;// background-color: #e7e5e7 !important;}.btn-box {width: 100%;display: flex;text-align: center;padding: 20rpx 0;border-top: 1px solid #bbb;}
</style>

使用它

	.popup-title {text-align: center;font-weight: 500;font-weight: bold;padding: 40rpx 0;border-bottom: 1px solid #bbb;}<u-popup ref="popupRef" mode="center" title="考试签名" background-color="#fff"><view class="popup-title"><text>考试签名</text></view><view><userSign></userSign></view></u-popup>const popupRef = ref()// 签名弹出层const togglePopup = () => {console.log('悬浮球 - 弹框出答题卡');popupRef.value.open('center')}
http://www.xdnf.cn/news/1380745.html

相关文章:

  • Flask测试平台开发,登陆重构
  • (二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵
  • 并发编程——05 并发锁机制之深入理解synchronized
  • 学习数据结构(13)二叉树链式结构下
  • 线程池及线程池单例模式
  • 带动态条件的模糊查询SQL
  • DINOv2 vs DINOv3 vs CLIP:自监督视觉模型的演进与可视化对比
  • LeetCode 3446. 按对角线进行矩阵排序
  • UE5提升分辨率和帧率的方法
  • 搭建私有云3步法:cpolar简化Puter本地云端配置
  • C# SIMD编程实践:工业数据处理性能优化案例
  • C++ 哈希概念版
  • 【实战笔记】OCI Ubuntu 24.04 + TigerVNC + XFCE + Chrome 开机自启全记录
  • 错误模块路径: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
  • 从卡顿到丝滑:大型前端项目 CSS 优化全攻略
  • [高并发系统设计] - 搭建高并发高可用的系统 - 学习与探究
  • 【大前端】React useEffect 详解:从入门到进阶
  • Shi-Tomasi 算法和 Harris 角点检测算法都是经典的角点检测方法,但它们在理论基础和实现细节上有一些区别。下面我将详细对比这两种算法。
  • List<Map<String, String>>最简单的遍历方式
  • 【传奇开心果系列】Flet框架带图标带交互动画的办公用品费用占比统计饼图自定义模板
  • GitHub 热榜项目 - 日榜(2025-08-28)
  • 达梦数据库-重做日志文件(一)
  • 云计算学习100天-第30天
  • 09- AI大模型-docker部署dify以及 dify的使用案例:公司智能助手(构建知识库)(上篇)
  • TDengine 数据订阅支持 MQTT 协议用户手册
  • 【SQL】计算一年内每个月份的周数据
  • 上海控安:WiFi网络安全攻击
  • SONiC 之 Testbed(2)Ansible
  • GeoScene Maps 完整入门指南:从安装到实战
  • Android稳定性问题的常见原因是什么