雖然寫的字有點(diǎn)丑,但這個(gè)手搓的簽字板代碼還是很酷的......
Hello,大家好,我是 Sunday。
前段時(shí)間有位同學(xué)問我:“公司項(xiàng)目中需要增加一個(gè)簽字板的功能”,問我如何進(jìn)行實(shí)現(xiàn)。
我說:“這種功能很簡(jiǎn)單呀,目前市面上有很多開源的庫(kù),比如:signature_pad
就可以直接引入實(shí)現(xiàn)”。
但是,該同學(xué)說自己公司的項(xiàng)目比較特殊,盡量不要使用 第三方的庫(kù),所以想要自己實(shí)現(xiàn),那怎么辦呢?
沒辦法!只能幫他實(shí)現(xiàn)一個(gè)了.
簽字板實(shí)現(xiàn)邏輯
簽字板的功能實(shí)現(xiàn)其實(shí)并不復(fù)雜,核心是 基于 canvas 的 2d
繪制能力,監(jiān)聽用戶 鼠標(biāo) 或者 手指 的移動(dòng)行為,完成對(duì)應(yīng)的 線繪制。
所以,想要實(shí)現(xiàn)簽字板那么必須要有一個(gè) canvas
,先看 html 的實(shí)現(xiàn)部分:
html
<body>
<!-- 畫板 -->
<canvas id="signature-pad" width="400" height="200"></canvas>
<!-- 控制器 -->
<div class="controls">
<select id="stroke-style">
<option value="pen">鋼筆</option>
<option value="brush">毛筆</option>
</select>
<button id="clear">清空</button>
</div>
<script src="script.js"></script>
</body>
我們可以基于以上代碼完成 HTML 布局,核心是兩個(gè)內(nèi)容:
canvas
畫布:它是完成簽字板的關(guān)鍵controls
控制器:通過它可以完成 畫筆切換 以及 清空畫布 的功能
css
css 相對(duì)比較簡(jiǎn)單,大家可以根據(jù)自己的需求進(jìn)行調(diào)整就可以了,以下是 css 大家可以作為參考:
* {
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100vh;
width: 100vw;
background-color: #f0f0f0;
overflow: hidden;
}
canvas {
border: 1px solid #000;
background-color: #fff;
}
.controls {
margin-top: 10px;
display: flex;
gap: 10px;
}
button,
select {
padding: 5px 10px;
cursor: pointer;
}
js
js 部分是整個(gè)簽字板的核心,我們需要在這里考慮較多的內(nèi)容,比如:
- 為了繪制更加平滑,我們需要使用
ctx.quadraticCurveTo
方法完成平滑過渡 - 為了解決移動(dòng)端手指滑動(dòng)滾動(dòng)條的問題,我們需要在
move
事件中通過e.preventDefault()
取消默認(rèn)行為 - 為了完成畫筆切換,我們需要監(jiān)聽
select
的change
事件,從而修改ctx.lineWidth
畫筆
最終得到的 js 代碼如下所示(代碼中提供的詳細(xì)的注釋):
document.addEventListener('DOMContentLoaded', function () {
// 獲取 canvas 元素和其 2D 上下文
var canvas = document.getElementById('signature-pad')
var ctx = canvas.getContext('2d')
var drawing = false // 標(biāo)志是否正在繪制
var lastX = 0,
lastY = 0 // 保存上一個(gè)點(diǎn)的坐標(biāo)
var strokeStyle = 'pen' // 初始筆畫樣式
// 開始繪制的函數(shù)
function startDrawing(e) {
e.preventDefault() // 阻止默認(rèn)行為,避免頁(yè)面滾動(dòng)
drawing = true // 設(shè)置為正在繪制
ctx.beginPath() // 開始新路徑
// 記錄初始點(diǎn)的位置
const { offsetX, offsetY } = getEventPosition(e)
lastX = offsetX
lastY = offsetY
ctx.moveTo(offsetX, offsetY) // 移動(dòng)畫筆到初始位置
}
// 繪制過程中的函數(shù)
function draw(e) {
e.preventDefault() // 阻止默認(rèn)行為,避免頁(yè)面滾動(dòng)
if (!drawing) return // 如果不是在繪制,直接返回
// 獲取當(dāng)前觸點(diǎn)位置
const { offsetX, offsetY } = getEventPosition(e)
// 使用貝塞爾曲線進(jìn)行平滑過渡繪制
ctx.quadraticCurveTo(
lastX,
lastY,
(lastX + offsetX) / 2,
(lastY + offsetY) / 2
)
ctx.stroke() // 實(shí)際繪制路徑
// 更新上一個(gè)點(diǎn)的位置
lastX = offsetX
lastY = offsetY
}
// 停止繪制的函數(shù)
function stopDrawing(e) {
e.preventDefault() // 阻止默認(rèn)行為
drawing = false // 結(jié)束繪制狀態(tài)
}
// 獲取事件中觸點(diǎn)的相對(duì)位置
function getEventPosition(e) {
// 鼠標(biāo)事件或者觸摸事件中的坐標(biāo)
const offsetX = e.offsetX || e.touches[0].clientX - canvas.offsetLeft
const offsetY = e.offsetY || e.touches[0].clientY - canvas.offsetTop
return { offsetX, offsetY }
}
// 鼠標(biāo)事件綁定
canvas.addEventListener('mousedown', startDrawing) // 鼠標(biāo)按下開始繪制
canvas.addEventListener('mousemove', draw) // 鼠標(biāo)移動(dòng)時(shí)繪制
canvas.addEventListener('mouseup', stopDrawing) // 鼠標(biāo)抬起停止繪制
canvas.addEventListener('mouseout', stopDrawing) // 鼠標(biāo)移出畫布停止繪制
// 觸摸事件綁定
canvas.addEventListener('touchstart', startDrawing) // 觸摸開始繪制
canvas.addEventListener('touchmove', draw) // 觸摸移動(dòng)時(shí)繪制
canvas.addEventListener('touchend', stopDrawing) // 觸摸結(jié)束時(shí)停止繪制
canvas.addEventListener('touchcancel', stopDrawing) // 觸摸取消時(shí)停止繪制
// 清除畫布的功能
document.getElementById('clear').addEventListener('click', function () {
ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空整個(gè)畫布
})
// 修改筆畫樣式的功能
document
.getElementById('stroke-style')
.addEventListener('change', function (e) {
strokeStyle = e.target.value // 獲取選中的筆畫樣式
updateStrokeStyle() // 更新樣式
})
// 根據(jù) strokeStyle 更新筆畫樣式
function updateStrokeStyle() {
if (strokeStyle === 'pen') {
ctx.lineWidth = 2 // 細(xì)線條
ctx.lineCap = 'round' // 線條末端圓角
} else if (strokeStyle === 'brush') {
ctx.lineWidth = 5 // 粗線條
ctx.lineCap = 'round' // 線條末端圓角
}
}
// 初始化默認(rèn)的筆畫樣式
updateStrokeStyle()
})
以上就是 純JS實(shí)現(xiàn)簽字板的完整代碼,大家可以直接組合代碼進(jìn)行使用,最終展示的結(jié)果如下:
圖片