簡単なペイントツールの作成
マウス操作とタッチイベントどちらにも対応したペイントツールです。
主な機能は、以下の通りです。
- マウスとタッチイベントの両方に対応
- 線の太さを選択可能
- 色を選択可能
- 描画内容のリセット
- 描画内容を画像として保存可能
Code
HTML / CSS
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/javascripts/paint.js"></script>
<title>Document</title>
<style>
canvas {
background-color: white;
border: solid 5px black;
}
.black {
background-color: black;
color: white;
}
.white {
background-color: white;
color: black;
}
.red {
background-color: red;
color: white;
}
.blue {
background-color: blue;
color: white;
}
.yellow {
background-color: yellow;
color: black;
}
</style>
</head>
<body>
<main>
<p>文字の太さ:
<select name="" id="fontweight">
<option value="1">1</option>
<option value="3" selected>3</option>
<option value="5">5</option>
</select>
</p>
<p>
色選択:
<select id="color" class="black">
<option value="black" class="black" selected>black</option>
<option value="white" class="white">white</option>
<option value="red" class="red">red</option>
<option value="blue" class="blue">blue</option>
<option value="yellow" class="yellow">yellow</option>
</select>
</p>
<p><button id="reset">リセット</button></p>
<canvas id="canvas" width="300" height="300"></canvas>
<div>
<h2>画像の保存</h2>
<p>背景色
<select name="" id="bgcolor">
<option value="transparent">透過</option>
<option value="white">white</option>
</select>
<button id="save">保存</button>
</p>
</div>
</main>
</body>
</html>
Javascript
window.addEventListener('load', function() {
// true: 描画中 false: 描画中でない
let flgDraw = false;
// 座標
let gX = 0;
let gY = 0;
// 描画色
let gColor = 'black';
let fontWeight = 3;
// イベント登録
// マウス
const canvas = document.getElementById('canvas');
canvas.addEventListener('mousedown', startDraw, false);
canvas.addEventListener('mousemove', Draw, false);
canvas.addEventListener('mouseup', endDraw, false);
// タッチ
canvas.addEventListener('touchstart', startDraw, false);
canvas.addEventListener('touchmove', Draw, false);
canvas.addEventListener('touchend', endDraw, false);
// セレクトボックス
const s = document.getElementById('color');
s.addEventListener('change', changeColor, false);
const select_fontweight = document.getElementById('fontweight');
select_fontweight.addEventListener('change', changefontWeight, false);
const resetBtn = document.getElementById('reset');
resetBtn.addEventListener('click', clearCanvas, false);
const saveBtn = document.getElementById('save');
saveBtn.addEventListener('click', saveImage, false);
// セレクトボックス変更時に色を変更する
function changeColor(){
const select = document.getElementById('color');
gColor = select.value;
const selectedOption = select.options[select.selectedIndex];
const computedStyle = window.getComputedStyle(selectedOption);
const color = computedStyle.color
const backgroundColor = computedStyle.backgroundColor;
document.getElementById('color').style.color = color;
document.getElementById('color').style.backgroundColor = backgroundColor;
}
// フォントウェイト変更時に線の太さを変更する
function changefontWeight(){
fontWeight = document.getElementById('fontweight').value;
}
function getXY(e) {
const rect = canvas.getBoundingClientRect();
let x, y;
if (e.touches) {
// タッチ操作時
x = e.touches[0].clientX - rect.left;
y = e.touches[0].clientY - rect.top;
} else {
// マウス操作時
x = e.offsetX;
y = e.offsetY;
}
return { x, y };
}
// 描画開始
function startDraw(e) {
const pos = getXY(e);
gX = pos.x;
gY = pos.y;
flgDraw = true;
if (e.touches) e.preventDefault(); // スクロール防止
}
// 描画
function Draw(e) {
if (!flgDraw) return;
const con = canvas.getContext('2d');
const pos = getXY(e);
con.lineWidth = fontWeight;
con.strokeStyle = gColor;
con.beginPath();
con.moveTo(gX, gY);
con.lineTo(pos.x, pos.y);
con.closePath();
con.stroke();
gX = pos.x;
gY = pos.y;
if (e.touches) e.preventDefault(); // スクロール防止
}
// 描画終了
function endDraw(e) {
flgDraw = false;
if (e && e.touches) e.preventDefault(); // スクロール防止
}
// キャンバスをクリア
function clearCanvas() {
const con = canvas.getContext('2d');
con.clearRect(0, 0, canvas.width, canvas.height);
}
// PNG保存処理
function saveImage() {
// 一時的なキャンバスを作成
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const bgColor = document.getElementById('bgcolor').value;
const tempCtx = tempCanvas.getContext('2d');
// 背景を白で塗りつぶす(透明防止)
tempCtx.fillStyle = bgColor;
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
// 元のキャンバス内容をコピー
tempCtx.drawImage(canvas, 0, 0);
// データURLを取得
const image = tempCanvas.toDataURL('image/png');
// ダウンロード処理
const link = document.createElement('a');
link.href = image;
link.download = 'canvas_drawing.png';
link.click();
}
});
セレクトメニューのスタイル操作
インラインスタイルで指定した場合
<select id="color" style="color:white;background-color:black">
<option value="white" style="background-color:black;color:white" selected>black</option>
<option value="white" style="background-color:white;color:black">white</option>
<option value="red" style="background-color:red;color:white">red</option>
<option value="blue" style="background-color:blue;color:white">blue</option>
<option value="yellow" style="background-color:yellow;color:black">yellow</option>
</select>
上記の option の sytle 属性の値を javascript で select タグに反映させるには、以下のようにします。
function changeColor(){
const select = document.getElementById('color');
gColor = select.value;
const selectedOption = select.options[select.selectedIndex];
const color = selectedOption.style.color
const backgroundColor = selectedOption.style.backgroundColor;
document.getElementById('color').style.color = color;
document.getElementById('color').style.backgroundColor = backgroundColor;
}
select.options | 全ての <option> 要素が入った配列のようなオブジェクト |
select.selectedIndex | 現在選ばれている <option> のインデックス番号 |
selectedOption.style | HTMLの style 属性から直接取得 |
外部CSSやクラスで指定されたスタイルを取得したい場合
CSSファイルや class で設定されたスタイルも含めて、最終的にブラウザが計算したスタイルを取得します。
<select id="color" class="black">
<option value="black" class="black" selected>black</option>
<option value="white" class="white">white</option>
<option value="red" class="red">red</option>
<option value="blue" class="blue">blue</option>
<option value="yellow" class="yellow">yellow</option>
</select>
/* CSS */
.black {
background-color: black;
color: white;
}
.white {
background-color: white;
color: black;
}
.red {
background-color: red;
color: white;
}
.blue {
background-color: blue;
color: white;
}
.yellow {
background-color: yellow;
color: black;
}
function changeColor(){
const select = document.getElementById('color');
gColor = select.value;
const selectedOption = select.options[select.selectedIndex];
const computedStyle = window.getComputedStyle(selectedOption);
const color = computedStyle.color
const backgroundColor = computedStyle.backgroundColor;
document.getElementById('color').style.color = color;
document.getElementById('color').style.backgroundColor = backgroundColor;
}
タッチイベント
touchstart / touchmove イベントからはe.offsetX/Y が取得できないためtouches[0].clientXやtouches[0].pageXを使う必要があります。
function getXY(e) {
const rect = canvas.getBoundingClientRect();
let x, y;
if (e.touches) {
// タッチ操作時
x = e.touches[0].clientX - rect.left;
y = e.touches[0].clientY - rect.top;
} else {
// マウス操作時
x = e.offsetX;
y = e.offsetY;
}
return { x, y };
}
タッチイベントで得られる clientX や clientY は 画面全体に対する座標のため Canvas 内の座標に変換するには、Canvas の「位置」を引く必要があります。
getBoundingClientRect() は、HTML要素の位置とサイズを取得するためのメソッドです。マウス操作なら offsetX / offsetY で済みますが、タッチ操作ではgetBoundingClientRect()が必要です。
以下の表は、戻り値の主なプロパティです。
プロパティ | 意味 |
---|---|
rect.top | 要素の上端が、画面の上端からどれだけ離れているか(px) |
rect.left | 要素の左端が、画面の左端からどれだけ離れているか(px) |
rect.right | 要素の右端が、画面左端からの距離 |
rect.bottom | 要素の下端が、画面上端からの距離 |
rect.width | 要素の幅(px) |
rect.height | 要素の高さ(px) |
画像の保存方法
以下は画像保存の最低限の機能を関数化する場合の例です。
function saveImage() {
const image = canvas.toDataURL('image/png'); // PNG画像としてデータURLを取得
const link = document.createElement('a');
link.href = image;
link.download = 'canvas_drawing.png'; // ファイル名
link.click(); // 自動でダウンロード実行
}
canvas のリサイズ
function resizeCanvas() {
const canvas = document.getElementById('canvas');
const lineWidth = 12; // ボーダーの幅を考慮
const dpr = window.devicePixelRatio || 1;
const width = window.innerWidth - lineWidth;
const height = window.innerHeight - lineWidth - 300;
// 見た目サイズ(CSS)
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// 実際の描画領域サイズ(Retina対応)
canvas.width = width * dpr;
canvas.height = height * dpr;
// コンテキストにスケーリング
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
}
resizeCanvas(); // 読み込み時の初期リサイズ
window.addEventListener('resize', resizeCanvas);