如何用HTML5 Canvas实现电子签名功能✍️

在这里插入图片描述

🤖 作者简介:水煮白菜王,一位资深前端劝退师 👻
👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。
感谢支持💕💕💕

🖌️电子签名作为数字化转型的重要环节,在前端可通过HTML5 Canvas轻松实现。本文将以HTML De示例🎨,手把手带您完成一个电子签名功能。

目录

  • 一、实现原理与核心技术
  • 二、核心代码实现解析
    • 2.1 画布初始化
    • 2.2 画笔样式配置
    • 2.3 设备兼容处理
    • 2.4 绘制逻辑实现
      • 起笔监听:
      • 移动绘制:
      • 收笔处理:
    • 2.5 功能按钮实现
      • 清空画布:
      • 保存签名:
      • 可加撤销功能:
  • 三、注意事项
  • 四、完整代码示例

一、实现原理与核心技术

通过HTML5 Canvas的2D绘图上下文实现轨迹捕捉,结合事件监听处理完成核心绘制功能。关键技术点包括:

  1. Canvas绘图API:Path路径操作
  2. 事件系统:鼠标/触摸事件统一处理
  3. 文件导出:Canvas转Blob对象

二、核心代码实现解析

2.1 画布初始化

设置600x300画布并获取2D上下文,建议根据屏幕尺寸动态调整画布大小。

const canvas = document.querySelector('canvas');
canvas.width = 500;
canvas.height = 300;
const ctx = canvas.getContext('2d');

2.2 画笔样式配置

通过lineCap和lineJoin实现自然的手写效果。

ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
ctx.lineCap = 'round'; // 圆角线头
ctx.lineJoin = 'round'; // 圆角连接

2.3 设备兼容处理

通过UA检测自动切换触摸/鼠标事件,实际项目中建议使用pointer events实现更优雅的兼容。

const mobileStatus = /Mobile|Android|iPhone/i.test(navigator.userAgent);

2.4 绘制逻辑实现

起笔监听:

function start(event) {const pos = mobileStatus ? event.changedTouches[0] : event;ctx.beginPath();ctx.moveTo(pos.pageX, pos.pageY);window.addEventListener(mobileStatus ? 'touchmove' : 'mousemove', draw);
}

移动绘制:

function draw(event) {const pos = mobileStatus ? event.changedTouches[0] : event;ctx.lineTo(pos.pageX, pos.pageY);ctx.stroke(); // 实时渲染路径
}

收笔处理:

function closeDraw() {window.removeEventListener('mousemove', draw);
}

2.5 功能按钮实现

清空画布:

function cancel() {ctx.clearRect(0, 0, canvas.width, canvas.height);
}

保存签名:

function save() {canvas.toBlob(blob => {const a = document.createElement('a');a.download = `${Date.now()}.png`;a.href = URL.createObjectURL(blob);a.click();});
}

可加撤销功能:

let history = [];
// 绘制时保存状态
history.push(ctx.getImageData(0,0,canvas.width,canvas.height));function undo() {if(history.length > 1) {history.pop();ctx.putImageData(history[history.length-1], 0,0);}
}

三、注意事项

  1. 性能优化:大数据量绘制建议使用requestAnimationFrame
  2. 跨域问题:若涉及图片合成需设置crossOrigin=“anonymous”
  3. 移动端适配:添加CSS样式防止触摸滚动
canvas {touch-action: none;background: #f8f8f8;
}

四、完整代码示例

<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>* {margin: 0 auto;padding: 0;}button { padding: 3px 5px;margin: 5px; }</style></head><body><canvas id="sign"></canvas><div><button onclick="cancel()">取消</button><button onclick="save()">保存</button></div></body><script>const canvas = document.querySelector('canvas');canvas.width = 600;canvas.height = 300;canvas.style.borderRadius = '5px';canvas.style.border = '1px solid #a6a9ad';const ctx = canvas.getContext('2d');// 高清屏适配const scale = window.devicePixelRatio;canvas.width = 600 * scale;canvas.height = 300 * scale;ctx.scale(scale, scale);ctx.lineWidth = 3; //线宽ctx.strokeStyle = 'black'; //线颜色ctx.lineCap = 'round'; //线条的结束端点样式ctx.lineJoin = 'round'; //两条线相交时,所创建的拐角类型// 检测移动设备const mobileStatus = /Mobile|Android|iPhone/i.test(navigator.userAgent);const start = (event) => {const { offsetX, offsetY, pageX, pageY } = mobileStatus? event.changedTouches[0]: event;ctx.beginPath(); //起始一条路径,或重置当前路径ctx.moveTo(pageX, pageY); //把路径移动到画布中的指定点,不创建线条window.addEventListener(mobileStatus ? 'touchmove' : 'mousemove', draw);};const draw = (event) => {const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;ctx.lineTo(pageX, pageY); //添加一个新点,然后在画布中创建从该点到最后指定点的线条ctx.stroke(); //绘制已定义的路径};const cloaseDraw = () => {window.removeEventListener('mousemove', draw);};window.addEventListener(mobileStatus ? 'touchstart' : 'mousedown', start);window.addEventListener(mobileStatus ? 'touchend' : 'mouseup', cloaseDraw);const cancel = () => {ctx.clearRect(0, 0, 600, 300); //在给定的矩形内清除指定的像素};const save = () => {canvas.toBlob((blob) => {const date = Date.now().toString();const a = document.createElement('a');a.download = `${date}.png`;a.href = URL.createObjectURL(blob);a.click();a.remove();});};</script>
</html>

实际项目中需结合后端实现签名验证、添加时间戳等扩展功能。可继续丰富代码逻辑:

  1. 添加Base64导出功能
  2. 实现笔锋效果(通过速度计算线宽)
  3. 添加本地存储自动保存

Canvas的绘图能力还能延伸应用于手写笔记、电子批注等场景。

在这里插入图片描述
如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀
在这里插入图片描述

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

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

相关文章

Uniapp项目运行到微信小程序、H5、APP等多个平台教程

摘要&#xff1a;Uniapp作为一款基于Vue.js的跨平台开发框架&#xff0c;支持“一次开发&#xff0c;多端部署”。本文将手把手教你如何将Uniapp项目运行到微信小程序、H5、APP等多个平台&#xff0c;并解析常见问题。 一、环境准备 在开始前&#xff0c;请确保已安装以下工具…

Python设计模式 - 建造者模式

定义 建造者模式是一种创建型设计模式&#xff0c;主要用于构建包含多个组成部分的复杂对象。它将对象的构建过程与表示分离&#xff0c;使得同样的构建过程可以创建不同的对象表示。 结构 抽象建造者&#xff08;Builder&#xff09;&#xff1a;声明创建产品的各个部件的方…

音乐API

https://neteasecloudmusicapi.vercel.app/docs/#/https://neteasecloudmusicapi.vercel.app/docs/#/ 使用实例 所有榜单内容摘要 说明 : 调用此接口,可获取所有榜单内容摘要 接口地址 : /toplist/detail 调用例子 : /toplist/detail 获取歌单所有歌曲 说明 : 由于网易云…

Jetpack Compose — 入门实践

一、项目中使用 Jetpack Compose 从此节开始,为方便起见,如无特殊说明,Compose 均指代 Jetpack Compose。 开发工具: Android Studio 1.1 创建支持 Compose 新应用 新版 Android Studio 默认创建新项目即为 Compose 项目。 注意:在 Language 下拉菜单中,Kotlin 是唯一可…

【day12】进程切换与调度:linux系统的幕后操控术

【Day12】进程切换与调度&#xff1a;linux系统的幕后操控术 进程优先级进程属性&#xff1a;UID进程属性&#xff1a;PRI和NI进程饥饿 竞争/独立/并行/并发进程切换进程调度&#xff08;O(1)调度算法&#xff09; 进程优先级 进程优先级的本质&#xff1a;衡量进程得到CPU资源…

STM32之BKP

VBAT备用电源。接的时候和主电源共地&#xff0c;正极接在一起&#xff0c;中间连接一个100nf的电容。BKP是RAM存储器。 四组VDD都要接到3.3V的电源上&#xff0c;要使用备用电池&#xff0c;就把电池正极接到VBAT&#xff0c;负极跟主电源共地。 TEMPER引脚先加一个默认的上拉…

mapbox高阶,结合threejs(threebox)添加管道

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️threebox Tube静态对象二、🍀使用thr…

Android15使用FFmpeg解码并播放MP4视频完整示例

效果: 1.编译FFmpeg库: 下载FFmpeg-kit的源码并编译生成安装平台库 2.复制生成的FFmpeg库so文件与包含目录到自己的Android下 如果没有prebuiltLibs目录,创建一个,然后复制 包含目录只复制arm64-v8a下

利用FatJar彻底解决Jar包冲突(三)

利用FatJar彻底解决Jar包冲突 Spring 容器的加载与隔离⽀持注解配置⽂件定位与容器初始化嵌套Spring容器的加载 隔离优化EagleEye traceId不⼀致问题原因解决 Spring 容器的加载与隔离 ⽀持注解 这个⽐较容易&#xff0c;主要是我们之前的应⽤不⽀持⼆⽅包内部的注解&#xf…

ThinkPHP8.0+MySQL8.0搭建简单实用电子证书查询系统

客户花了100元买了一个系统&#xff0c;开始不能导入&#xff0c;到处找人帮忙解决。给解决能导入了&#xff0c;不能修改&#xff0c;满足不了用户的需求。用户一狠心&#xff0c;花200块钱&#xff0c;叫我给他定制了一个电子证书查询系统。还免费给部署到服务器。惭愧惭愧……

越早越好!8 个反直觉的金钱真相|金钱心理学

很多人都追求财富自由&#xff0c;但成功的人少之又少。 这可能是因为&#xff0c;人们往往忽略了一些金钱的真相和常识。 01 金钱常识 & 真相 为了构建健康的金钱观&#xff0c;我读了一本有点反直觉&#xff0c;有点像鸡汤&#xff0c;但都是财富真相的书。 来自 Morg…

文本转语音-音画适时推送rtsp并播放

文本语音 rtsp适时播放叫号系统的底层逻辑 发布Linux, unix socket 和window win32做为音频源的 python10下的(ffmpeg version 7.1) 可运行版本. 这两天在弄这个&#xff0c;前2篇是通过虚拟声卡&#xff0c;达到了最简单的一个逻辑&#xff0c;播放文本就从声卡发声&#xff0…

类和对象(中)

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成…

搜索插入位置(js实现,LeetCode:35)

给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2: 输入…

【问题记录】如何编译nv_peer_memory模块?依赖OFED的4个目录和2类文件?如何解决没有rdma/peer_mem.h文件?

背景 GDR&#xff1a;GPUDirect RDMA。这项新技术在 GPU 内存之间直接与 NVIDIA HCA/NIC 设备之间提供直接的 P2P&#xff08;点对点&#xff09;数据路径。这显着降低了 GPU-GPU 通信延迟&#xff0c;并完全减轻了 CPU 的负担。nv_peer_memory模块是网卡提供给GPU使用GDR技术…

3.6c语言

#define _CRT_SECURE_NO_WARNINGS #include <math.h> #include <stdio.h> int main() {int sum 0,i,j;for (j 1; j < 1000; j){sum 0;for (i 1; i < j; i){if (j % i 0){sum i;} }if (sum j){printf("%d是完数\n", j);}}return 0; }#de…

李彦宏:紧抓AI智能体爆发元年机遇 推动新质生产力加快发展

来源 人民网 3月5日公布的政府工作报告&#xff0c;“智能”一词被提及10次&#xff0c;“大模型”则首次被写入。作为大模型和人工智能领域的领军企业&#xff0c;我们感到非常振奋&#xff0c;同时深感责任重大。 报告在介绍今年政府工作任务时提出&#xff0c;“因地制宜发…

基于Python Django的人脸识别上课考勤系统(附源码,部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

5G基本概念

作者:私语茶馆 1. 5G应用场景概述 1.1.5G应用场景 ITU域2015年定义了三大应用场景:eMBB(增强型移动宽带)、uRLLC(低时延高可靠通信)、mMTC(海量物联网通信); emBB:Enhanced Mobile Broadband ,移动互联网应用,是4G MBB(移动宽带)的升级,主要侧重于网络速率、带…

VSCode+AI编程生态实战:从环境配置到智能编码的全栈指南

Python环境的搭建&#xff0c;我们选择了Anaconda环境&#xff0c;有Python环境&#xff0c;接下来要搭建当然是AI辅助编程环境了。在搭建这个之前&#xff0c;或者说大家在打算安装VSCode之前&#xff0c;需要先检查自已电脑是否已经安装了VSCode&#xff0c;因为这个软件是Wi…