オウルです。
前回の続きです。Canvas API は JavaScript と HTML の canvas によって矩形、三角形、線、円弧、曲線などを描くことができます。もっと高度になるとアニメーション、写真加工、リアルタイム動画処理などにも使用することが可能とのこと。今回の JavaScript と Canvas でお絵かきを実装することにより、基礎を体験できます。
お絵かき
まず、パソコンでお絵かきする場合のマウスイベントです。
Drag Event
mousemove
マウスが移動したときに要素でイベント発生。
mousedown
要素上でマウスのボタンが押下されたときにイベント発生。
mouseup
要素上でマウスのボタンが離されたときにイベント発生。
mouseout
マウスのカーソル(移動)が要素、または子要素(その子の1つ)に含まれなくなると、要素でイベント発生。
HTML CSS
ローカル環境
ブラウザ | Google Chrome |
JavaScript | ES2015~ |
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!-- お絵かきの操作ボタン --> <div class="flex"> <input type="button" id="edit" name="edit" value="はじめる" /> <input type="button" id="eraser" name="eraser" value="消しゴム" /> <input type="button" id="clear" name="clear" value="ぜんぶ消す" /> <input type="button" id="end" name="end" value="おわり" /> </div> <div id="canvasmenu" class="flex"> <!-- カラー --> <div class="icon-color bc-e91e63"></div> <div class="icon-color bc-ffeb3b"></div> <div class="icon-color bc-ff4801"></div> <div class="icon-color bc-536dfe"></div> <div class="icon-color bc-009688"></div> <div class="icon-color bc-00e676"></div> <div class="icon-color bc-555555"></div> <!-- サイズ --> <div class="icon-size icon-size-sm" data-size="5"><span>小</span></div> <div class="icon-size icon-size-md" data-size="10"><span>中</span></div> <div class="icon-size icon-size-lg" data-size="14"><span>大</span></div> </div> <div id="canvas-wrapper"> <canvas id="my-canvas"></canvas> </div> <!-- マウスの位置 --> <div class="flex"> <div>X : </div> <div id="xpostion"></div> </div> <div class="flex"> <div>Y : </div> <div id="ypostion"></div> </div> |
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<style type="text/css"> .flex { display: flex; flex-flow: row nowrap; /* default */ justify-content: flex-start; /* default */ margin-bottom: 10px; } .flex > input { margin-right: 10px; } .icon-color { width: 30px; height: 30px; border-radius: 50%; margin-right: 10px; } .bc-e91e63 { background-color: #e91e63; } .bc-ffeb3b { background-color: #ffeb3b; } .bc-ff4801 { background-color: #ff4801; } .bc-536dfe { background-color: #536dfe; } .bc-009688 { background-color: #009688; } .bc-00e676 { background-color: #00e676; } .bc-555555 { background-color: #555555; } .icon-size { width: 30px; height: 30px; border-radius: 50%; text-align: center; margin-right: 10px; } .icon-size span { vertical-align: middle; } .icon-size-sm { background-color: #555555; color: #ffffff; } .icon-size-md { background-color: #555555; color: #ffffff; } .icon-size-lg { background-color: #555555; color: #ffffff; } #canvas-wrapper { width: 640px; height: 380px; border: solid 1px black; } </style> |
JavaScript
クラス定義
ECMAScript 2015 で導入された JavaScript クラスで実装します。JavaScript クラスは、プロトタイプベース継承の糖衣構文です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
class DrawingTools { constructor(canvasId) { this._canvas = document.getElementById(canvasId); this._canvas.width = this._canvas.parentNode.clientWidth; this._canvas.height = this._canvas.parentNode.clientHeight; // canvas の 2D レンダリングコンテキスト(Canvas API の CanvasRenderingContext2D インターフェイス)を取得 this._ctx = this._canvas.getContext('2d'); this._mouse = { x: null, y: null }; this._defalutDraw = { size: 14, color: '#555555', alpha: 1.0, lineCap: 'round' }; this._toolState = { initialized: false } } initialized() { const draw = (event) => { const x = event.layerX; const y = event.layerY; lineTo(x, y); document.getElementById("xpostion").textContent = x; document.getElementById("ypostion").textContent = y; } const lineTo = (x, y) => { this._ctx.globalAlpha = this._defalutDraw.alpha; this._ctx.lineCap = this._defalutDraw.lineCap; this._ctx.lineWidth = this._defalutDraw.size; this._ctx.strokeStyle = this._defalutDraw.color; // マウス継続値によって、直線の moveTo(スタート地点)を決定 if (this._mouse.x === null || this._mouse.y === null) { // 現在のマウス位置をスタート位置 this._ctx.moveTo(x, y); } else { // ゴール位置を次のスタート位置とする this._ctx.moveTo(this._mouse.x, this._mouse.y); } // 現在の描画位置から x と y で指定した位置に、線を描く this._ctx.lineTo(x, y); this._ctx.stroke(); this._mouse.x = x; this._mouse.y = y; } const closePath = () => { // 最終座標と開始座標を結んでパスを閉じる this._ctx.closePath(); this._mouse.x = null; this._mouse.y = null; } // [はじめる]ボタン const mousedownHandler = { handleEvent: (event) => { if (event.button === 0) { // 新しいパス(複数の点が線によって結ばれている)を作成 this._ctx.beginPath(); } } }; const mousemoveHandler = { handleEvent: (event) => { if (event.buttons === 1 || event.witch === 1) { draw(event); } } }; const mouseupHandler = { handleEvent: (event) => { closePath(); } }; const mouseoutHandler = { handleEvent: (event) => { closePath(); } }; const startDrawingHandler = { handleEvent: () => { this._canvas.addEventListener('mousedown', mousedownHandler, { once: false, passive: true, capture: false }); this._canvas.addEventListener('mousemove', mousemoveHandler, { once: false, passive: true, capture: false }); this._canvas.addEventListener('mouseup', mouseupHandler, { once: false, passive: true, capture: false }); this._canvas.addEventListener('mouseout', mouseoutHandler, { once: false, passive: true, capture: false }); } }; const edit = document.getElementById('edit'); edit.addEventListener('click', startDrawingHandler, { once: false, passive: true, capture: false }); // [消しゴム]ボタン const startEraserHandler = { handleEvent: () => { this._canvas.globalCompositeOperation = 'destination-out'; this._defalutDraw.color = '#FFFFFF'; } }; const eraser = document.getElementById('eraser'); eraser.addEventListener('click', startEraserHandler, { once: false, passive: true, capture: false }); // [ぜんぶ消す]ボタン const clearDrawingHandler = { handleEvent: () => { this._ctx.beginPath(); this._ctx.fillStyle = "#ffffff"; this._ctx.globalAlpha = 1.0; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } }; const clear = document.getElementById('clear'); clear.addEventListener('click', clearDrawingHandler, { once: false, passive: true, capture: false }); // [おわり]ボタン const endDrawingHandler = { handleEvent: () => { this._canvas.removeEventListener('mousedown', mousedownHandler, { once: false, passive: true, capture: false }); this._canvas.removeEventListener('mousemove', mousemoveHandler, { once: false, passive: true, capture: false }); this._canvas.removeEventListener('mouseup', mouseupHandler, { once: false, passive: true, capture: false }); this._canvas.removeEventListener('mouseout', mouseoutHandler, { once: false, passive: true, capture: false }); } }; const end = document.getElementById('end'); end.addEventListener('click', endDrawingHandler, { once: false, passive: true, capture: false }); // [カラー]ボタン const activateColorHandler = { handleEvent: (event) => { const style = window.getComputedStyle(event.target); const color = style.getPropertyValue('background-color'); this._defalutDraw.color = color; } }; const menus = document.getElementById('canvasmenu'); const colorElems = menus.querySelectorAll('.icon-color'); for (const elem of colorElems) { elem.addEventListener('click', activateColorHandler, { once: false, passive: true, capture: false }); } // [サイズ]ボタン const activateSizeHandler = { handleEvent: (event) => { const size = event.target.parentNode.getAttribute('data-size'); this._defalutDraw.size = size; } }; const sizeElems = menus.querySelectorAll('.icon-size'); for (const elem of sizeElems) { elem.addEventListener('click', activateSizeHandler, { once: false, passive: true, capture: false }); } this._toolState.initialized = true; } } window.addEventListener('load', () => { const tools = new DrawingTools('my-canvas'); tools.initialized(); }); |
デモ
PC 版
See the Pen
VwYEyQy by owl (@blue-owl)
on CodePen.
スマホ 版
スマホ版は、Chrome DevTools のモバイル ビューポートのシミュレーションで確認しました。
See the Pen
LYEgerz by owl (@blue-owl)
on CodePen.
次は、お絵かきをデータとして保存するための前準備として HTMLCanvasElement.toDataURL() を使って画像として表示してみます。