签名的实现功能

我们要实现签名:
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实现电子签名的更多相关文章

  1. 讲解Canvas中的一些重要方法

    Canvas所提供的各种方法根据功能来看大致可以分为几类: 第一是以drawXXX为主的绘制方法: 第二是以clipXXX为主的裁剪方法: 第三是以scale.skew.translate和rotat ...

  2. 【HTML5 2】《html5 开发精要与实例讲解》 step1 -- 导读

    一.教程重点:以 综合性案例 为导向,辅之以 精要知识点 二.内容概况: 第1部分:通过 大小型案例 对 各重要知识点 进行详细讲解 第2部分:jWebSocket.RGraph.WebGL 三个重要 ...

  3. canvas学习总结三:绘制路径-线段

    Canvas绘图环境中有些属于立即绘制图形方法,有些绘图方法是基于路径的. 立即绘制图形方法仅有两个strokeRect(),fillRect(),虽然strokezText(),fillText() ...

  4. Android查缺补漏(View篇)--自定义View利器Canvas和Paint详解

    上篇文章介绍了自定义View的创建流程,从宏观上给出了一个自定义View的创建步骤,本篇是上一篇文章的延续,介绍了自定义View中两个必不可少的工具Canvas和Paint,从细节上更进一步的讲解自定 ...

  5. 8-2 canvas专题-线条样式

    8-2 canvas专题-线条样式 学习要点 对第五章知识进行简单的回顾和总结 进一步讲解canvas绘图相关的知识点 第八章内容介绍 在第八章中我们将对以前的知识进行简单的回顾,着重对canvas绘 ...

  6. canvas详解---绘制弧线

    Draw an arc context.arc(centerx,centery,radius,startingAngle,endingAngle,anticlockwise=false); 参数一是圆 ...

  7. 通过游戏学javascript系列第一节Canvas游戏开发基础

    本节教程通过一个简单的游戏小例子,讲解Canvas的基础知识. 最终效果: 点击移动的方块,方块上的分数会增加,方块的行进方向会改变,并且方块的速度会增加. 在线演示 源码 HTML5引入了canva ...

  8. android 开发 View _10_ Path之基本操作

    转载地址:http://www.gcssloop.com/customview/Path_Basic/ 安卓自定义View进阶-Path之基本操作 在上一篇Canvas之图片文字中我们了解了如何使用C ...

  9. 3.1 js基本概念

    js中的语法大量借鉴于C以及其他类C语言(Java,Perl). js中一切(变量.函数名.操作符等等)都区分大小写.如"var a;"中的变量a跟"var A;&quo ...

  10. Log4Net学习【一】

    如果项目上过线的话,那你一定知道Log是多么重要.为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题.这时打一手好Log的重要性绝不亚于写一手好代码.项目出问题时,你要能拿出L ...

随机推荐

  1. SpringMVC 解决中文乱码问题以及前后端Json格式数据交互的测试

    1.今日遇到的报错: 跳转网页出现404原因: 1.检查project structure里面的webapp路径是否正确: 2.检查project structure里的artifaccts里的WEB ...

  2. 现代 CSS 解决方案:CSS 原生支持的三角函数

    在 CSS 中,存在许多数学函数,这些函数能够通过简单的计算操作来生成某些属性值,例如 : calc():用于计算任意长度.百分比或数值型数据,并将其作为 CSS 属性值. min() 和 max() ...

  3. windows笔记本极致省电指南

    用到了三个软件:parkcontrol,process lasso,quickCPU parkcontrol -调整CPU的运行核心和频率,可以设置离电的时候关闭一些CPU核心数,以达到省电的目的 插 ...

  4. Python 安装教程,新手入门(超详细)含Pycharm开发环境安装教程

    目录 一.Python介绍 二.Python安装教程 (一)Python的下载 (二)Python的安装 三.Pycharm开发工具的安装 (一)Pycharm介绍 (二)Pycharm的下载 (三) ...

  5. Kubernetes(k8s)使用ingress发布服务

    目录 一.系统环境 二.前言 三.Kubernetes ingress简介 四.Ingress vs NodePort vs LoadBalancer 五.安装部署Nginx Ingress Cont ...

  6. 实例讲解Flink 流处理程序编程模型

    摘要:在深入了解 Flink 实时数据处理程序的开发之前,先通过一个简单示例来了解使用 Flink 的 DataStream API 构建有状态流应用程序的过程. 本文分享自华为云社区<Flin ...

  7. 从零实现俄罗斯方块(c语言+思路分析)

    俄罗斯方块 文章说明: 本文大部分参考至俄罗斯方块(C语言实现)_c语言俄罗斯方块_2021dragon的博客-CSDN博客,本人经过修改编辑,改变了文章的一些思路顺序,使得新手便于理解(个人想法). ...

  8. 记一次 .NET 某埋线管理系统 崩溃分析

    一:背景 1. 讲故事 经常有朋友跟我反馈,说看你的文章就像看天书一样,有没有一些简单入手的dump 让我们先找找感觉,哈哈,今天就给大家带来一篇入门级的案例,这里的入门是从 WinDbg 的角度来阐 ...

  9. 如何使用 Terraform 和 Git 分支有效管理多环境?

    作者|Sumeet Ninawe 翻译|Seal软件 链接|https://spacelift.io/blog/terraform-environments 通常我们使用 Terraform 将我们的 ...

  10. VSCode中打开NodeJS项目自动切换对应版本的配置

    这几年搞了不少静态站点,有的是Hexo的,有的是VuePress的.由于不同的主题对于NodeJS的版本要求不同,所以本机上不少NodeJS的版本. 关于如何管理多个NodeJS版本,很早之前就写过用 ...