详情讲解canvas实现电子签名
签名的实现功能
我们要实现签名:
1.我们首先要鼠标按下,移动,抬起。经过这三个步骤。
我们可以实现一笔或者连笔。
按下的时候我们需要移动画笔,可以使用 moveTo 来移动画笔。
e.pageX,e.pageY来获取坐标位置
移动的时候我们进行绘制
ctx.lineTo(e.pageX,e.pageY)
ctx.stroke()
通过开关flag来判断是否绘制
2.我们可以调整画笔的粗细
3.当我们写错的时候,可以撤回上一步
4.重置整个画板
5.点击保存的时候,可以生成一张图片
6.base64转化为file
实现签名
<!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>
*{
padding: 0;
margin: 0;
}
#canvas {
border: 2px dotted #ccc;
background-repeat: no-repeat;
background-size: 80px;
}
</style>
</head>
<body>
<div class="con-box">
<canvas id="canvas" width="600px" height="400px"></canvas>
<button id="save-btn" onclick="saveHandler">保存</button>
<button id="reset-btn" onclick="resetHandler">重置</button>
</div>
</body>
<script>
// 获取canvas元素的DOM对象
const canvas=document.getElementById('canvas')
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'
let flag= false
// 注册鼠标按下事件
canvas.addEventListener('mousedown',e=>{
console.log('按下',e.pageX,e.pageY)
flag=true
// 获取按下的那一刻鼠标的坐标,同时移动画笔
ctx.moveTo(e.pageX,e.pageY)
})
// 注册移动事件
canvas.addEventListener('mousemove',e=>{
console.log('移动')
if(flag){
// 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
ctx.lineTo(e.pageX,e.pageY)
// 使用 stroke() 方法真正地画线
ctx.stroke()
}
})
// 注册抬起事件
canvas.addEventListener('mouseup',e=>{
console.log('抬起')
flag=false
})
</script>
</html>
鼠标移入canvas就会触发事件
通过上面的图,我们发现了一个点。
那就是鼠标移入canvas所在的区域。
就会触发移动事件的代码。
这是为什么呢?
因为我们在移入的时候注册了事件,因此就会触发。
现在我们需要优化一下:将移动事件,抬起事件放在按下事件里面
同时,当鼠标抬起的时候,移除移动事件和抬起事件。【不移除按下事件】
这里可能有的小伙伴会问?
为什么抬起的时候不移除按下事件。
因为:代码从上往下执行,当我们移除抬起事件后,我们只能绘画一次了。
当我们移除事件时,我们就不需要开关 flag 了。
删除flag的相关代码
<script>
// 获取canvas元素的DOM对象
const canvas=document.getElementById('canvas')
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'
// 注册鼠标按下事件
canvas.addEventListener('mousedown',mousedownFun)
// 按下事件
function mousedownFun(e){
console.log('按下',e.pageX,e.pageY)
// 获取按下的那一刻鼠标的坐标,同时移动画笔
ctx.moveTo(e.pageX,e.pageY)
// 注册移动事件
canvas.addEventListener('mousemove',mousemoveFun)
// 注册抬起事件
canvas.addEventListener('mouseup',mouseupFun)
}
// 移动事件
function mousemoveFun(e){
console.log('移动')
// 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
ctx.lineTo(e.pageX,e.pageY)
// 使用 stroke() 方法真正地画线
ctx.stroke()
}
// 抬起事件
function mouseupFun(e){
console.log('抬起')
// 移除移动事件
canvas.removeEventListener('mousemove', mousemoveFun)
// 移除抬起事件
canvas.removeEventListener('mouseup', mouseupFun)
}
</script>
发现bug-鼠标不按下也可以绘制笔画
我们发现鼠标移出canvas所在区域后。
然后在移入进来,鼠标仍然可以进行绘制。(此时鼠标已经是松开了)
这很明显是一个bug。这个bug产生的原因在于:
鼠标移出canvas所在区域后没有移出移动事件
// 鼠标移出canvas所在的区域事件-处理鼠标移出canvas所在区域后
// 然后移入不按下鼠标也可以绘制笔画
canvas.addEventListener('mouseout',e=>{
// 移除移动事件
canvas.removeEventListener('mousemove', mousemoveFun)
})
如何设置画笔的粗细
我们想要调整画笔的粗细。
需要使用 ctx.lineWidth 属性来设置画笔的大小默认是1。
我们用 <input type="range" class="range" min="1" max="30" value="1" id="range">
来调整画笔。
因为我们我们调整画笔后,线条的大小就会发生改变。
因此我们在每次按下的时候都需要开始本次绘画。
抬起的时候结束本次绘画,
这样才能让不影响上一次画笔的大小。
核心的代码
<input type="range" class="range" min="1" max="30" value="1" id="range">
// 获取设置画笔粗细的dom元素
let range = document.querySelector("#range");
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 按下事件
function mousedownFun(e){
console.log('按下',e.pageX,e.pageY)
// 开始本次绘画(与画笔大小设置有关)
ctx.beginPath();
// 设置画笔的粗细
ctx.lineWidth = range.value || 1
}
// 抬起事件
function mouseupFun(e){
// 结束本次绘画(与画笔大小设置有关)
ctx.closePath();
console.log('抬起')
}
撤回上一步
1. 先声明一个数组. let historyArr=[]
按下的时候记录当前笔画起始点的特征(颜色 粗细 位置)
currentPath = {
color: ctx.strokeStyle,
width: ctx.lineWidth,
points: [{ x: e.offsetX, y: e.offsetY }]
}
2.按下移动的时候记录每一个坐标点[点连成线]
currentPath.points.push({ x: e.offsetX, y: e.offsetY });
3.鼠标抬起的时候说明完成了一笔(连笔)
historyArr.push(currentPath);
4.点击撤销按钮的时候删除最后一笔
5.然后重新绘制之前存储的画笔
<!-- 核心代码 -->
<button id="revoke">撤销</button>
let historyArr = [] //保存所有的操作
let currentPath = null;
let revoke=document.querySelector("#revoke");
// 按下事件
function mousedownFun(e){
// 开始本次绘画(与画笔大小设置有关)
ctx.beginPath();
// 设置画笔的粗细
ctx.lineWidth = range.value || 1
// 获取按下的那一刻鼠标的坐标,同时移动画笔
ctx.moveTo(e.pageX,e.pageY)
// 记录当前笔画起始点的特征(颜色 粗细 位置)
currentPath = {
color: ctx.strokeStyle,
width: ctx.lineWidth,
points: [{ x: e.offsetX, y: e.offsetY }]
}
}
// 移动事件
function mousemoveFun(e){
ctx.lineTo(e.pageX,e.pageY)
currentPath.points.push({ x: e.offsetX, y: e.offsetY });
ctx.stroke()
}
// 抬起事件
function mouseupFun(e){
historyArr.push(currentPath);
ctx.closePath();
}
// 撤销按钮点击事件
revoke.addEventListener('click', e => {
if (historyArr.length === 0) return;
// 删除最后一条的记录
historyArr.pop()
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPaths(historyArr);
});
// 画所有的路径
function drawPaths(paths) {
paths.forEach(path => {
ctx.beginPath();
ctx.strokeStyle = path.color;
ctx.lineWidth = path.width;
ctx.moveTo(path.points[0].x, path.points[0].y);
// path.points.slice(1) 少画 与 path.points 区别是少画一笔和正常笔数
console.log('path',path)
path.points.slice(1).forEach(point => {
ctx.lineTo(point.x, point.y);
});
ctx.stroke();
});
}
重置整个画布
<button id="reset" >重置</button>
// 重置整个画布
reset.addEventListener('click',e=>{
//清空整个画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
})
ps:清空画布的主要运用了ctx.clearRect这个方法
保存
保存图片主要是通过 canvas.toDataURL 生成的是base64
然后通过a标签进行下载
saveBtn.addEventListener('click',()=>{
let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
let link = document.createElement('a');
link.download = "tupian";
link.href = imgURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
生成file文件发送给后端
// base64转化为file文件
function base64changeFile (urlData, fileName) {
// split将按照','字符串按照,分割成一个数组,
// 这个数组通常包含了数据类型(MIME type)和实际的数据。
// 数组的第1项是类型 第2项是数据
const arr = urlData.split(',')
// data:image/png;base64
const mimeType = arr[0].match(/:(.*?);/)[1]
console.log('类型',mimeType)
// 将base64编码的数据转换为普通字符串
const bytes = atob(arr[1])
let n = bytes.length
// 创建了一个新的Uint8Array对象,并将这些字节复制到这个对象中。
const fileFormat = new Uint8Array(n)
while (n--) {
fileFormat[n] = bytes.charCodeAt(n)
}
return new File([fileFormat], fileName, { type: mimeType })
}
fileBtn.addEventListener('click',()=>{
let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
let file = base64changeFile(imgURL,'qianMing')
console.log('file',file)
})
全部代码
<!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>
*{
padding: 0;
margin: 0;
}
#canvas {
border: 2px dotted #ccc;
}
</style>
</head>
<body>
<div class="con-box">
<canvas id="canvas" width="600px" height="400px"></canvas>
<input type="range" class="range" min="1" max="30" value="1" id="range">
<button id="revoke">撤销</button>
<button id="save-btn">保存</button>
<button id="file">转化为file</button>
<button id="reset" >重置</button>
</div>
</body>
<script>
// 获取canvas元素的DOM对象
const canvas=document.getElementById('canvas')
// 获取设置画笔粗细的dom元素
let range = document.querySelector("#range");
let revoke=document.querySelector("#revoke");
let reset=document.querySelector("#reset");
let saveBtn=document.querySelector("#save-btn");
let fileBtn=document.querySelector("#file");
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'
let historyArr = [] //保存所有的操作
let currentPath = null;
// 注册鼠标按下事件
canvas.addEventListener('mousedown',mousedownFun)
// 按下事件
function mousedownFun(e){
console.log('按下',e.pageX,e.pageY)
// 开始本次绘画(与画笔大小设置有关)
ctx.beginPath();
// 设置画笔的粗细
ctx.lineWidth = range.value || 1
// 获取按下的那一刻鼠标的坐标,同时移动画笔
ctx.moveTo(e.pageX,e.pageY)
// 记录当前笔画起始点的特征(颜色 粗细 位置)
currentPath = {
color: ctx.strokeStyle,
width: ctx.lineWidth,
points: [{ x: e.offsetX, y: e.offsetY }]
}
// 注册移动事件
canvas.addEventListener('mousemove',mousemoveFun)
// 注册抬起事件
canvas.addEventListener('mouseup',mouseupFun)
}
// 移动事件
function mousemoveFun(e){
console.log('移动')
// 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
ctx.lineTo(e.pageX,e.pageY)
// 记录画笔的移动的每一个坐标位置
currentPath.points.push({ x: e.offsetX, y: e.offsetY });
// 使用 stroke() 方法真正地画线
ctx.stroke()
}
// 抬起事件
function mouseupFun(e){
// 一笔结束后存储起来
historyArr.push(currentPath);
console.log('historyArr',historyArr)
// 结束本次绘画(与画笔大小设置有关)
ctx.closePath();
console.log('抬起')
// 移除移动事件
canvas.removeEventListener('mousemove', mousemoveFun)
// 移除抬起事件
canvas.removeEventListener('mouseup', mouseupFun)
}
// 鼠标移出canvas所在的区域事件-处理鼠标移出canvas所在区域后,然后移入不按下鼠标也可以绘制笔画
canvas.addEventListener('mouseout',e=>{
// 移除移动事件
canvas.removeEventListener('mousemove', mousemoveFun)
})
// 撤销按钮点击事件
revoke.addEventListener('click', e => {
if (historyArr.length === 0) return;
// 删除最后一条的记录
historyArr.pop()
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPaths(historyArr);
});
// 重置整个画布
reset.addEventListener('click',e=>{
//清空整个画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
})
// 保存为图片
saveBtn.addEventListener('click',()=>{
let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
console.log('imgURL',imgURL)
let link = document.createElement('a');
link.download = "tupian";
link.href = imgURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
// 画所有的路径
function drawPaths(paths) {
console.log(11,paths)
paths.forEach(path => {
ctx.beginPath();
ctx.strokeStyle = path.color;
ctx.lineWidth = path.width;
ctx.moveTo(path.points[0].x, path.points[0].y);
// path.points.slice(1) 少画 与 path.points 区别是少画一笔和正常笔数
console.log('path',path)
path.points.slice(1).forEach(point => {
ctx.lineTo(point.x, point.y);
});
ctx.stroke();
});
}
// base64转化为file文件
function base64changeFile (urlData, fileName) {
// split将按照','字符串按照,分割成一个数组,
// 这个数组通常包含了数据类型(MIME type)和实际的数据。
// 数组的第1项是类型 第2项是数据
const arr = urlData.split(',')
// data:image/png;base64
const mimeType = arr[0].match(/:(.*?);/)[1]
console.log('类型',mimeType)
// 将base64编码的数据转换为普通字符串
const bytes = atob(arr[1])
let n = bytes.length
// 创建了一个新的Uint8Array对象,并将这些字节复制到这个对象中。
const fileFormat = new Uint8Array(n)
while (n--) {
fileFormat[n] = bytes.charCodeAt(n)
}
return new File([fileFormat], fileName, { type: mimeType })
}
fileBtn.addEventListener('click',()=>{
let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
let file = base64changeFile(imgURL,'qianMing')
console.log('file',file)
})
</script>
</html>
详情讲解canvas实现电子签名的更多相关文章
- 讲解Canvas中的一些重要方法
Canvas所提供的各种方法根据功能来看大致可以分为几类: 第一是以drawXXX为主的绘制方法: 第二是以clipXXX为主的裁剪方法: 第三是以scale.skew.translate和rotat ...
- 【HTML5 2】《html5 开发精要与实例讲解》 step1 -- 导读
一.教程重点:以 综合性案例 为导向,辅之以 精要知识点 二.内容概况: 第1部分:通过 大小型案例 对 各重要知识点 进行详细讲解 第2部分:jWebSocket.RGraph.WebGL 三个重要 ...
- canvas学习总结三:绘制路径-线段
Canvas绘图环境中有些属于立即绘制图形方法,有些绘图方法是基于路径的. 立即绘制图形方法仅有两个strokeRect(),fillRect(),虽然strokezText(),fillText() ...
- Android查缺补漏(View篇)--自定义View利器Canvas和Paint详解
上篇文章介绍了自定义View的创建流程,从宏观上给出了一个自定义View的创建步骤,本篇是上一篇文章的延续,介绍了自定义View中两个必不可少的工具Canvas和Paint,从细节上更进一步的讲解自定 ...
- 8-2 canvas专题-线条样式
8-2 canvas专题-线条样式 学习要点 对第五章知识进行简单的回顾和总结 进一步讲解canvas绘图相关的知识点 第八章内容介绍 在第八章中我们将对以前的知识进行简单的回顾,着重对canvas绘 ...
- canvas详解---绘制弧线
Draw an arc context.arc(centerx,centery,radius,startingAngle,endingAngle,anticlockwise=false); 参数一是圆 ...
- 通过游戏学javascript系列第一节Canvas游戏开发基础
本节教程通过一个简单的游戏小例子,讲解Canvas的基础知识. 最终效果: 点击移动的方块,方块上的分数会增加,方块的行进方向会改变,并且方块的速度会增加. 在线演示 源码 HTML5引入了canva ...
- android 开发 View _10_ Path之基本操作
转载地址:http://www.gcssloop.com/customview/Path_Basic/ 安卓自定义View进阶-Path之基本操作 在上一篇Canvas之图片文字中我们了解了如何使用C ...
- 3.1 js基本概念
js中的语法大量借鉴于C以及其他类C语言(Java,Perl). js中一切(变量.函数名.操作符等等)都区分大小写.如"var a;"中的变量a跟"var A;&quo ...
- Log4Net学习【一】
如果项目上过线的话,那你一定知道Log是多么重要.为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题.这时打一手好Log的重要性绝不亚于写一手好代码.项目出问题时,你要能拿出L ...
随机推荐
- C语言之环形队列
一.环形队列的优势 环形队列是一种特殊的队列,它可以解决普通队列在使用时空间利用不充分的问题.在环形队列中,当队列满时,队列的尾指针指向队列的起始位置,而不是指向队列的最后一个元素.这样可以在不浪费空 ...
- phpstudy-pikachu-字符型注入(get)
在查询栏输入1,点击查询获得查询格式 ?name=1'&submit=查询 *捷径 ' or 1=1 --+ *非捷径 ?name=1' and 1=2 --+ 2--+&submit ...
- KO之间互相调用
需求 假设有两个KO,命名为moduleA.KO,moduleB.KO,现在要实现在moduleB.KO中调用moduleA.KO中的函数. 实现 ModuleA实现 源码: #include < ...
- 数据库SQL复习
数据库SQL介绍 Def:SQL是一种极其高效的数据库系统语言:可以实现对数据库中的数据进行增删改查等操作 增加操作:使用create命令: 可以create table 可以create View ...
- SQL后半部和JDBC
SQL后半部 排序order by asc 升序desc 降序select *from 表名 order by 列名 asc ; select *from 表名 order by 列名 asc , 列 ...
- 基于SqlSugar的开发框架循序渐进介绍(30)-- 整合客户关系管理系统模块功能
以前在随笔<Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展示 >的几篇随笔中介绍过基于WInform开发框架开发的CRM系统,系统的功能主要也是围绕着客户相 ...
- 记录一些不知道哪里冒出来的 idea(可能已存在)
2022/7/4 给定一张 \(n\) 个点的有权无向图和 \(m\) 个关键点,求其生成树满足每个点到离它最近的关键点的距离之和最小. 输出该生成树边权和. 加强一下: 给出一个 \(N\) 个点 ...
- 【leetcode】#647 回文子串 Rust Solution
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串.具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串. 示例 1:输入:"abc"输出:3解释 ...
- 韩顺平Spring体系化笔记(内含ioc,aop,动态代理等底层原理)
Spring Spring 核心学习内容 IOC.AOP. JdbcTemplate.声明式事务 1.Spring 几个重要概念 Spring 可以整合其他的框架(Spring 是管理框架的框架) S ...
- Python编程和数据科学中的数据处理:如何从数据中提取有用的信息和数据
目录 引言 数据分析和数据处理是数据科学和人工智能领域的核心话题之一.数据科学家和工程师需要从大量的数据中提取有用的信息和知识,以便更好地理解和预测现实世界中的事件.本文将介绍Python编程和数据科 ...