共计 5308 个字符,预计需要花费 14 分钟才能阅读完成。
之前学了 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 = 5
ctx.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 a
const 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 标签实现图片下载。
如果喜爱或有所帮忙的话,心愿能点赞关注,激励一下作者。
如果文章有不正确或存疑的中央,欢送评论指出。
正文完