之前学了 canvas,当初线上签字那么风行,就想着本人实现一个玩玩
打算实现一个蕴含以下性能的写字板
- 应用鼠标写字
- 调整画笔粗细
- 批改画笔色彩
- 撤销性能
- 清空画布的性能
- 生成图片,下载签名
动态构造
动态构造很简略,就间接列出来不做解释了
HTML
<div> <div id="settings"> <div> 字体大小: <select id="fontSize"></select> </div> <div> 字体色彩: <input type="color" id="fontColor" /> </div> <button id="undo">撤销</button> <button id="clear">清空画布</button> <button id="download">生成图片</button> </div> <canvas width="800" height="300" id="tablet"></canvas></div>
CSS
#settings { display: flex; margin: auto; margin-top: 100px; line-height: 27px; justify-content: center; gap: 20px;}#tablet { display: block; margin: auto; margin-top: 10px; background-color: #eee;}
gap 属性可能有人不意识,能够设置 flex/grad 布局子元素的距离
动态页面就是这么个样子,字体大小的选项是用 js 生成的
接下来咱们来写 JS
性能实现
初始化 canvas
咱们先初始化 canvas 和其 2d 上下文 ctx
设置线宽为 5,线的末端和连接处均为圆角
const tablet = document.getElementById('tablet')const ctx = tablet.getContext('2d')ctx.lineWidth = 5ctx.lineCap = 'round'ctx.lineJoin = 'round'
字体大小
而后咱们生成字体大小的选项,容许 1-16px,默认为 5px
const fontSize = document.getElementById('fontSize')for (let i = 1; i <= 16; i++) { const option = document.createElement('option') option.value = i option.innerText = i + 'px' fontSize.appendChild(option) if (i == 5) { option.selected = true }}fontSize.addEventListener('change', (e) => { ctx.lineWidth = e.target.value})
字体色彩
从 input 读取色彩,设置线色
const fontColor = document.getElementById('fontColor')fontColor.addEventListener('change', (e) => { ctx.strokeStyle = e.target.value})
写字
写字分为三局部
- 鼠标落下:开始写字,对应
mousedown
事件 - 鼠标挪动:正在写字,对应
mousemove
事件 - 鼠标抬起:完结写字,对应
mouseup
事件
写字无非就是许多点连成的线,在写字过程中,记录鼠标上一点的地位,将其与以后点连起来
我集体习惯在鼠标按下时注册事件,鼠标松开时革除事件;还有一种实现形式是用一个变量管制 mouseover
是否执行
把 mouseup
事件绑定在 document
上,又加了一个 mouseenter
事件,使得鼠标来到画布再回来能够持续写。
let prex, prey // 记录上一点的地位const mousedown = (e) => { if (e.button != 0) return // 不是左键间接返回 prex = e.offsetX prey = e.offsetY ctx.beginPath() // 开始门路 ctx.moveTo(prex, prey) // 注册事件 tablet.addEventListener('mousemove', mousemove) document.addEventListener('mouseup', mouseup) tablet.addEventListener('mouseenter', mouseenter)}const mousemove = (e) => { ctx.lineTo(e.offsetX, e.offsetY) ctx.stroke()}const mouseup = () => { // 敞开门路 ctx.closePath() // 革除事件 tablet.removeEventListener('mousemove', mousemove) document.removeEventListener('mouseup', mouseup) tablet.removeEventListener('mouseenter', mouseenter)}const mouseenter = (e) => { // 从内部回到画布,扭转上一点的地位,持续书写 prex = e.offsetX prey = e.offsetY ctx.moveTo(prex, prey)}tablet.addEventListener('mousedown', mousedown)
撤销
想要实现撤销性能,就要保留之前的状态,能够在开始写字时保留此时的画布状态,在须要撤销时复原
思考到 imageData
内容较大,咱们这里只实现了一步的撤销性能
let imageData // 图片数据const mousedown = (e) => { imageData = ctx.getImageData(0, 0, tablet.width, tablet.height) ……}const undo = document.getElementById('undo')undo.addEventListener('click', () => { ctx.putImageData(imageData, 0, 0)})
清空画布
清空画布很简略,clearRect
就好了
const clear = document.getElementById('clear')clear.addEventListener('click', () => { ctx.clearRect(0, 0, tablet.width, tablet.height)})
下载图片
canvas 生成 DateURL,而后利用 a 标签下载
let aconst download = document.getElementById('download')download.addEventListener('click', () => { if (!a) { a = document.createElement('a') } a.href = tablet.toDataURL('image/png') a.download = '签名.png' a.click()})
残缺代码
<!DOCTYPE html><html lang="zh-cmn-Hans"><head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style type="text/css"> #settings { display: flex; margin: auto; margin-top: 100px; line-height: 27px; justify-content: center; gap: 20px; } #tablet { display: block; margin: auto; margin-top: 10px; background-color: #eee; } </style></head><body> <div> <div id="settings"> <div> 字体大小: <select id="fontSize"></select> </div> <div> 字体色彩: <input type="color" id="fontColor" /> </div> <button id="undo">撤销</button> <button id="clear">清空画布</button> <button id="download">生成图片</button> </div> <canvas width="800" height="300" id="tablet"></canvas> </div> <script> const tablet = document.getElementById('tablet') const ctx = tablet.getContext('2d') ctx.lineCap = 'round' ctx.lineJoin = 'round' ctx.lineWidth = 5 ctx.strokeStyle = '#000' // 生成字体大小选项 const fontSize = document.getElementById('fontSize') for (let i = 1; i <= 16; i++) { const option = document.createElement('option') option.value = i option.innerText = i + 'px' if (i == 5) { option.selected = true } fontSize.appendChild(option) } fontSize.addEventListener('change', (e) => { ctx.lineWidth = e.target.value }) const fontColor = document.getElementById('fontColor') fontColor.addEventListener('change', (e) => { ctx.strokeStyle = e.target.value }) let imageData let prex, prey const mousedown = (e) => { if (e.button != 0) return // 不是左键间接返回 imageData = ctx.getImageData(0, 0, tablet.width, tablet.height) prex = e.offsetX prey = e.offsetY ctx.beginPath() ctx.moveTo(prex, prey) tablet.addEventListener('mousemove', mousemove) document.addEventListener('mouseup', mouseup) tablet.addEventListener('mouseenter', mouseenter) } const mousemove = (e) => { ctx.lineTo(e.offsetX, e.offsetY) ctx.stroke() } const mouseup = () => { tablet.removeEventListener('mousemove', mousemove) document.removeEventListener('mouseup', mouseup) tablet.removeEventListener('mouseenter', mouseenter) } const mouseenter = (e) => { prex = e.offsetX prey = e.offsetY ctx.moveTo(prex, prey) } tablet.addEventListener('mousedown', mousedown) const undo = document.getElementById('undo') undo.addEventListener('click', () => { ctx.putImageData(imageData, 0, 0) }) const clear = document.getElementById('clear') clear.addEventListener('click', () => { ctx.clearRect(0, 0, tablet.width, tablet.height) }) let a const download = document.getElementById('download') download.addEventListener('click', () => { if (!a) { a = document.createElement('a') } a.href = tablet.toDataURL() a.download = '签名.png' a.click() }) </script></body></html>
结语
至此写字板就实现了,都是 canvas 的常识,最初借助了 a 标签实现图片下载。
如果喜爱或有所帮忙的话,心愿能点赞关注,激励一下作者。
如果文章有不正确或存疑的中央,欢送评论指出。