おえかきツール
2024年6月25日
みなさんこんにちは。
ケミストのWeb担当みやのです。
「Web」記事では、これまでに得たWebに関する知識を記録として残していきたいと思います。
今回はcanvasを使った「お絵描きツール」を作ってみました。
筆談したいけどペンも紙もない!という時に役に立つかもしれません。
とりあえず見様見真似
参考:パソニュー|canvasでのお絵かきソフトの作り方と実例
お絵描きソフトの作り方をいろいろ調べていたのですが、スマホは非対応っていうケースが結構ありました。
上記サイトはスマホにも対応していたので、参考にさせていただきました。
HTMLとJSは以下の通りです。
<canvas id="canvas" width="640px" height="480px"></canvas>
<ul id="color-select">
<li style="background-color:#000"></li>
<li style="background-color:#285ff4"></li>
<li style="background-color:#64c466"></li>
<li style="background-color:#f7ce46"></li>
<li style="background-color:#eb4d3d"></li>
<li style="background-color:#fff"></li>
</ul>
線の太さ<input type="range" min="10" max="100" value="10" step="10" id="lineWidth"><span id="lineNum">10</span>
透明度<input type="range" min="0" max="100" value="100" id="alpha"><span id="alphaNum">100</span>
<button id="undo">元に戻す</button>
<button id="clear">消 去</button>
<button onclick="save()">保 存</button>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var mouse = {
x: 0,
y: 0,
x1: 0,
y1: 0,
color: "black"
};
var draw = false;
canvas.addEventListener("mousemove", function (e) {
var rect = e.target.getBoundingClientRect();
ctx.lineWidth = document.getElementById("lineWidth").value;
ctx.globalAlpha = document.getElementById("alpha").value / 100;
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
if (draw === true) {
ctx.beginPath();
ctx.moveTo(mouseX1, mouseY1);
ctx.lineTo(mouseX, mouseY);
ctx.lineCap = "round";
ctx.stroke();
mouseX1 = mouseX;
mouseY1 = mouseY;
}
});
canvas.addEventListener("mousedown", function (e) {
draw = true;
mouseX1 = mouseX;
mouseY1 = mouseY;
undoImage = ctx.getImageData(0, 0, canvas.width, canvas.height);
});
canvas.addEventListener("mouseup", function (e) {
draw = false;
});
lineWidth.addEventListener("mousemove", function () {
var lineNum = document.getElementById("lineWidth").value;
document.getElementById("lineNum").innerHTML = lineNum;
});
alpha.addEventListener("mousemove", function () {
var alphaNum = document.getElementById("alpha").value;
document.getElementById("alphaNum").innerHTML = alphaNum;
});
$('li').click(function () {
ctx.strokeStyle = $(this).css('background-color');
});
$('#clear').click(function (e) {
if (!confirm('本当に消去しますか?')) return;
e.preventDefault();
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
$('#undo').click(function (e) {
ctx.putImageData(undoImage, 0, 0);
});
function save() {
var can = canvas.toDataURL("image/png");
can = can.replace("image/png", "image/octet-stream");
window.open(can, "save");
}
var finger = new Array;
for (var i = 0; i < 10; i++) {
finger[i] = {
x: 0,
y: 0,
x1: 0,
y1: 0,
color: "rgb(" + Math.floor(Math.random() * 16) * 15 + "," + Math.floor(Math.random() * 16) * 15 + "," + Math.floor(Math.random() * 16) * 15 + ")"
};
}
canvas.addEventListener("touchstart", function (e) {
e.preventDefault();
var rect = e.target.getBoundingClientRect();
ctx.lineWidth = document.getElementById("lineWidth").value;
ctx.globalAlpha = document.getElementById("alpha").value / 100;
undoImage = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var i = 0; i < finger.length; i++) {
finger[i].x1 = e.touches[i].clientX - rect.left;
finger[i].y1 = e.touches[i].clientY - rect.top;
}
});
canvas.addEventListener("touchmove", function (e) {
e.preventDefault();
var rect = e.target.getBoundingClientRect();
for (var i = 0; i < finger.length; i++) {
finger[i].x = e.touches[i].clientX - rect.left;
finger[i].y = e.touches[i].clientY - rect.top;
ctx.beginPath();
ctx.moveTo(finger[i].x1, finger[i].y1);
ctx.lineTo(finger[i].x, finger[i].y);
ctx.lineCap = "round";
ctx.stroke();
finger[i].x1 = finger[i].x;
finger[i].y1 = finger[i].y;
}
});
lineWidth.addEventListener("touchmove", function () {
var lineNum = document.getElementById("lineWidth").value;
document.getElementById("lineNum").innerHTML = lineNum;
});
alpha.addEventListener("touchmove", function () {
var alphaNum = document.getElementById("alpha").value;
document.getElementById("alphaNum").innerHTML = alphaNum;
});
む、難しい・・・
パソコンだとmousedown、mouseup、mousemoveで、スマホだとtouchstartとtouchmoveなんだな、ということくらいしかわかりません。
さて、このコードをいろいろいじっていきましょう。
「元に戻す」は一手だけ元に戻すことができます。
透明度変更バーはうまく調整できなかったので今回は非表示にしています。
canvasの大きさ設定
いつものようにwidthを%指定したいところですがcanvasには無効でした。
canvasをさらにdivで囲って、JSでcanvasの幅を親要素に合わせるという方法があるみたいです。
<div id="wrapper">
<canvas id="canvas"></canvas>
</div>
#wrapper {
overflow: hidden;
width: 98%;
height: 350px;
border: 1px solid #ccc;
margin-top: 20px;
margin-right: auto;
margin-left: auto;
}
var w = $('#wrapper').width();
var h = $('#wrapper').height();
$('#canvas').attr('width', w);
$('#canvas').attr('height', h);
ただし、パソコンで画面幅を変えたりスマホの縦と横を持ち替えたりではwrapperの幅は変わってもcanvasの幅は変わらないので、1回リロードする必要があるようです。この方法では高さ調節はうまくいきませんでした。
参考:ARAKAZE NOTE|メモ:canvasタグのサイズを可変にするときの注意点
上記サイトを参考に全画面バージョンも作ってみました。
var theCanvas = document.getElementById('canvas');
function canvas_resize() {
var windowInnerWidth = window.innerWidth;
var windowInnerHeight = window.innerHeight;
theCanvas.setAttribute('width', windowInnerWidth);
theCanvas.setAttribute('height', windowInnerHeight);
}
window.addEventListener('resize', canvas_resize, false);
canvas_resize();
画面幅や高さを変更すると描いた内容がリセットされてしまうので注意してください。
色変更
参考元ではかなりたくさんのカラーパレットがありましたが、とりあえず黒、青、緑、黄色、赤、白を設置してみました。
色を選択する方法は他にはカラーコードの記事で紹介したinput type="color"というのがあります。これも組み込んでみましょう。
参考:TKGBlog|【JavaScript】Canvasでカラーピッカーを使用して、線の色を簡単に変更する方法
<ul id="color-select">
<li><input type="color" id="js-colorBox"></li>
<li style="background-color:#000"></li>
<li style="background-color:#285ff4"></li>
<li style="background-color:#64c466"></li>
<li style="background-color:#f7ce46"></li>
<li style="background-color:#eb4d3d"></li>
<li style="background-color:#fff"></li>
</ul>
let lineColor = '#000000'
const color = document.getElementById('js-colorBox');
color.addEventListener('change', ()=> {
lineColor = color.value;
ctx.strokeStyle = lineColor;
});
ただinput type="color"はiPhoneではかなり使いやすかったのに対し、パソコンではイマイチだったので(※個人の感想です)画面幅1199px以下ではinput type="color"、画面幅1200px以上ではカラーパレットを表示させるように設定しています。
消しゴム追加
参考:このコードわからん|canvasで透明な線(消しゴム)を書きたい
どうせ白背景なんだから白色=消しゴムということにしようと思っていましたが、そうでした、ダークモードがあるんでした。
白色パレットの代わりに消しゴムを設置することにします。ついでに消しゴムのアイコンも入れてみました。
<ul id="color-select">
<li><input type="color" id="js-colorBox"></li>
<li style="background-color:#000"></li>
<li style="background-color:#285ff4"></li>
<li style="background-color:#64c466"></li>
<li style="background-color:#f7ce46"></li>
<li style="background-color:#eb4d3d"></li>
<li style="background-color:#fff" id="eraser"><i class="fas fa-eraser"></i></li>
</ul>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
let lineColor = '#000000'
const color = document.getElementById('js-colorBox');
color.addEventListener('change', () => {
ctx.globalCompositeOperation = 'source-over';
lineColor = color.value;
ctx.strokeStyle = lineColor;
});
$('li').click(function () {
ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = $(this).css('background-color');
});
$('#eraser').click(function () {
ctx.globalCompositeOperation = 'destination-out';
});
・
・
・
いろいろ試したのですがなかなか思うように動きませんでした。消しゴムにしたらもうペンに戻せなかったり。ヤケクソでさっきの色変更のコード共々JSの上の方に持ってきたらなんとか動きました。
保存ボタンを変更
参考元の「保存」ボタンはパソコンのみ対応していましたが、スマホでも保存する方法がないか探してみました。
参考:「松原ガレッジ」のブログ|canvas タグで作成した画像をダウンロードさせるボタンを作る(透過あり/透過なしも選べる実装)
<button onClick="downloadImage(false)">保 存</button>
const downloadImage = (transparent = true) => {
const canvas = document.querySelector('#canvas');
let targetCanvas = canvas;
if(!transparent) {
const dummyCanvas = document.createElement("canvas");
dummyCanvas.width = canvas.width
dummyCanvas.height = canvas.height
const dummyCtx = dummyCanvas.getContext('2d');
dummyCtx.fillStyle = "#FFFFFF";
dummyCtx.fillRect(0, 0, canvas.width, canvas.height);
dummyCtx.drawImage(canvas, 0, 0);
targetCanvas = dummyCanvas
}
const dummyLink = document.createElement("a");
dummyLink.type = 'application/octet-stream';
dummyLink.download = `drawing.png`
dummyLink.href = targetCanvas.toDataURL('image/png')
dummyLink.click()
}
今回は「透過なしPNG」をダウンロードさせるようにしてみました。
onClick="downloadImage()"とすると「透過PNG」になるみたいです。
iPhoneだと「ファイル」の「ダウンロード」に保存されます。
今後の課題
・ちょん押しで点が描けるようにしたい。
・線の太さ変更はレンジスライダーではなくラジオボタンがいいかも。
・クレヨンやチョークのような線を引けるようにしたい。
・選択中の色(または消しゴム)が視覚的にわかるようにしたい。
・消しゴムとinput type="color"の切り替えがうまくいかない(色を変更しないとペンにならない)
・「元に戻す」を何回もしたい。
・スマホで保存先を選びたい。