See the Pen Canvas手寫Demo by 喇賽人 (@weber87na) on CodePen.
今天沒啥 fu 一直想搞個小畫家之類的, 還有之前在搞啥 FineReport 長官很想要有啥手寫簽名, 無聊就寫看看
本來覺得沒啥, 實際上也是要 try 才曉得一堆雷 XD
手機效果
先說電腦版, 電腦版用到 mousedown mouseup mousemove 事件
我的想法是用 mousedown mouseup 搭配 isDrawing 這個 flag 當作是否正在手寫
如果 mousemove 觸發的話則把它畫出來, 這裡可以直接拿到 offsetX offsetY 這兩個座標
這裡最關鍵就是當 isDrawing 時畫線, 反之讓他的 ctx.beginPath() 還原, 才可以正常做出想要的效果
1 | canvas.addEventListener('mousemove', (e) => { |
最後呼叫 drawLine 就可以畫出來了, 這裡的 drawLine 搞比較久, 換了好幾種作法畫起來都會是一點一點的, 解鎖小畫家筆刷的原理 XD
另外如果每次都用 ctx.beginPath() 就會有怪小怪小的 bug`
1 | function drawLine(ctx, point) { |
手機版則是要依靠 touchstart touchend touchmove 這三個事件, 他們拿座標的方法不太一樣
另外就是要呼叫 ctx.beginPath 的寫法也不太一樣, 實測要下面這樣才正常
1 | canvas.addEventListener( |
最後存檔我直接偷懶用 ChatGPT 來生, 印象中以前搞 openlayers 好像也寫過把很多圖層變成一張圖的
當時折磨得半死還有 CORS 問題, 現在有 AI 來搞這些真的好快阿
1 | btnSave.addEventListener('click', () => { |
Undo/Redo
後來又搞個 Undo/Redo 效果
See the Pen Canvas手寫Demo(可 Undo/Redo) by 喇賽人 (@weber87na) on CodePen.
Undo 主要是依靠 pop 這個函數, 把最後一筆彈出去改變 array 大小
此時 array 的最後一筆即為上一步所繪製的圖
1 | if (imageListUndo.length > 0) { |
Redo 本身不難, 但萬一中間又執行其他動作會發生奇怪的現象
可以觀察小畫家先畫兩筆然後 Undo 之後又接著畫, 就可以得到正確邏輯
為了因應這個操作, 必須要有 canRedo 這個變數來決定可否使用 Redo 的功能
所以當執行 Undo 時將 canRedo flag 設為 true
1 | if (event.ctrlKey && (event.key === 'z' || event.key === 'Z')) { |
萬一中間有新增筆畫的話需要於 mouseup 加上 canRedo 這個判斷, 需要把 canRedo 回歸為 false 並且 imageListRedo 清空
1 | canvas.addEventListener('mouseup',(e) => { |
最後看到本身的邏輯, 一樣使用 pop 把 imageListRedo 彈出來的最後一筆加入回 imageListUndo 的 array 即可完成
1 | document.addEventListener('keydown', function (event) { |