作成日:2025年05月26日
手書きツールを作ろう(2)

描画動作
1クリックして始点を決めて、もう1クリックで終点を決める
自分で書いておいて何ですが、ちょっと感覚的に使いづらいです
フリーラインの場合はマウスを押してから上げるまでの1クリックの動作中で描画を実行しますので、その処理との不統一が原因でしょう
何より、タッチ操作の場合では未確定時の情報が見られない点が良くありません
クリック(タッチ)中に描画を実行する様に、前回の直線・四角・円の描画処理を書き変えてみましょう
直線を引く(改)
では、まずは直線の処理を変更します
処理のイメージは下記の通りです
- マウスを押した時点で始点を決定して描画を開始する
- 押しながらマウスを動かしたら、未確定状態で描画イメージを表示する
- マウスを上げたら描画イメージを確定する
では、実際に書いてみましょう
HTML
<!DOCTYPE html> <html lang='ja'> <head> <meta charset='UTF-8'> <meta http-equiv='content-language' content='ja'> <meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=1'> </head> <body> <canvas id='handWriteBox' width=325 height=100 style='display:block; border:1px solid #000;'></canvas> <div style='display:flex; gap:10px; margin-top:10px;'> <input type='button' id='boxClear' value='Clear' style='padding:.3em .5em; font-size:16px; width:80px; height:32px; color:#000;'> </div> </body> </html>
Javascript
const hwb = document.getElementById('handWriteBox'); const ctx = hwb.getContext('2d'); let writing = false; let lastPosition = { x: null, y: null }; let baseImage = null; ctx.lineCap = 'round'; ctx.lineWidth = 2; function beforeDraw() { if (undoDataStack.length >= STACK_MAX) { undoDataStack.pop(); } undoDataStack.unshift(ctx.getImageData(0, 0, hwb.width, hwb.height)); } function draw(x, y) { ctx.putImageData(undoDataStack[0], 0, 0); ctx.beginPath(); ctx.moveTo(lastPosition.x, lastPosition.y); ctx.lineTo(x, y); ctx.stroke(); ctx.closePath(); }; function lineCancel() { if(writing && baseImage !== null) { ctx.putImageData(baseImage, 0, 0); } writing = false; }; hwb.addEventListener('mousedown', function(e){ beforeDraw(); writing = true; lastPosition.x = e.layerX - hwb.getBoundingClientRect().left; lastPosition.y = e.layerY - hwb.getBoundingClientRect().top; }); hwb.addEventListener('mouseout', lineCancel); hwb.addEventListener('mouseup', (e) => { writing = false; lastPosition = { x: null, y: null }; }); hwb.addEventListener('mousemove', function(e) { if(writing){ draw( e.layerX - hwb.getBoundingClientRect().left, e.layerY - hwb.getBoundingClientRect().top ); } }); hwb.addEventListener('touchstart', function(e){ beforeDraw(); writing = true; lastPosition.x = e.touches[0].clientX - hwb.getBoundingClientRect().left; lastPosition.y = e.touches[0].clientY - hwb.getBoundingClientRect().top; }); hwb.addEventListener('touchcancel', lineCancel); hwb.addEventListener('touchend', (e) => { writing = false; lastPosition = { x: null, y: null }; }); hwb.addEventListener('touchmove', (e) => { if(writing){ draw( e.touches[0].clientX - hwb.getBoundingClientRect().left, e.touches[0].clientY - hwb.getBoundingClientRect().top ); e.preventDefault(); } }); document.getElementById('boxClear').onclick = function(){ ctx.clearRect(0, 0, hwb.width, hwb.height); };
どうでしょうか
個人的には、こちらの挙動の方が直観的に分かりやすい気がします
円を描く(改)
次は円を描く処理を同様に書き直してみます
基本的には直線の場合と同じで、描画処理だけを変更します
Javascript
function draw(x, y) { ctx.putImageData(undoDataStack[0], 0, 0); let w = Math.abs(x - lastPosition.x); let h = Math.abs(y - lastPosition.y); let size = Math.max(w, h); ctx.beginPath(); ctx.arc(lastPosition.x, lastPosition.y, size, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); };
これで円がドラッグ処理で描けるようになりました
尚、これは塗りつぶしになっていますが、縁取りだけにする事も可能です
Javascript
function draw(x, y) { ctx.putImageData(undoDataStack[0], 0, 0); let w = Math.abs(x - lastPosition.x); let h = Math.abs(y - lastPosition.y); let size = Math.max(w, h); ctx.beginPath(); ctx.arc(lastPosition.x, lastPosition.y, size, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); };
四角を描く(改)
最後は四角です
これも描画処理を変えるだけなのですが、一応書いておきます
塗り潰し四角
Javascript
function draw(x, y) { ctx.putImageData(undoDataStack[0], 0, 0); ctx.fillRect(lastPosition.x, lastPosition.y, x - lastPosition.x, y - lastPosition.y); };
縁取り四角
Javascript
function draw(x, y) { ctx.putImageData(undoDataStack[0], 0, 0); ctx.strokeRect(lastPosition.x, lastPosition.y, x - lastPosition.x, y - lastPosition.y); };
FileMakerサンプル
以上で手書きツールの作成は完了です
ここまでの機能を集約したサンプルを公開していますので、ご興味がありましたらこちらからダウンロードしてご確認ください

以上で今日のメモ書きは終了です
内容はいかがでしたか?
もしご意見やご要望、誤りの指摘などありましたら、下記フォームよりお気軽にご連絡ください