签名的实现功能
我们要实现签名: 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>