【jQuery】お絵描きツールCanvasでブラウザ上に作ってみた
こんにちはクリアメモリです!
HTMLのCanvasというコードを使うことによって、WEB上にお絵かきできるサイトを作成することができます。
Canvasの使い方が分かれば、直線や図形を描けるようになるのでチェックしてみてください。
はじめに
今回紹介する方法では「jQuery」を使います。
こちらの記事で紹介している方法を参考にして、jQueryを使う準備をしておいてください。
動画でも解説しているので、確認してみてください。
cssの装飾によって多少違いは出てきますが、今回の方法で以下のようなキャンバスが完成します。
このようにキャンバスの中であれば、どこにでも絵を描くことができます。
また、今回の実装では「線の太さ」「色」「外枠のみの図形」「塗りつぶされた図形」を変更できるようにしてみました。
ではまず「HTML」で、サイト構成を準備していきましょう!
HTMLを準備しよう
今回は以下のようなコードにしてみました。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="<a class="keyword" href="http://d.hatena.ne.jp/keyword/utf-8">utf-8</a>"> <link rel="stylesheet" href="style.css"> <script src="jquery.js"></script> <script src="sample19.js"></script> </head> <body> <canvas id="canvas" width="800" height="500"></canvas> <div class="canvas"> <ul id="pen" class="content">> <li class="width" id="one"></li> <li class="width" id="two"></li> <li class="width" id="three"></li> <li class="width" id="four"></li> <li class="width" id="five"></li> </ul> <ul class="content" id="color">> <li class="color_name" id="blue"></li> <li class="color_name" id="black"></li> <li class="color_name" id="red"></li> <li class="color_name" id="white"></li> <li class="color_name" id="green"></li> <li class="color_name" id="yellow"></li> </ul> <ul id="fig_c" class="fig"> <li id="circle" class="fig_list">○</li> <li id="circle_fill" class="fig_list>●</li> </ul> <ul id="fig_s" class="fig"> <li id="square" class="fig_list">◻︎</li> <li id="square_fill" class="fig_list">◼︎</li> </ul> </div> </body> </html>
このコードでは「線の太さ」を変更できるボタンを1から5まで作成してみました。
あとで紹介しますが、javascriptを使って「ボタンにマウスが乗ったら」線の太さを選択するボタンが表示されるようになっています。
また、「線や図形の色」を変更できるボタンを幾つか用意してあります。
他にも
- 枠だけの丸
- 塗りつぶした丸
- 枠だけの四角
- 塗りつぶした四角
を描画することができます。
ではHTMLで作成した要素を「css」からカスタマイズしていきましょう!
cssで要素を装飾しよう
冒頭で紹介したような見た目にするには、以下のようなコードを作成します。
#canvas{ border:10px solid black; border-radius: 20px; } .canvas{ position: relative; top: -100px; } #pen{ position: absolute; background-color: green; width: 280px; height: 30px; margin: 20px; padding: 10px; max-width: 30px; top: -400px; left: 0; border: 2px solid black; overflow: hidden; font-size: 30px; font-weight: bold; text-align: left; color: white; line-height: 28px; } #pen > li{ position: absolute; border: 2px solid gray; background: #fff; list-style-type: none; border-radius: 20px; float: left; } #one{ left: 50px; top: 20px; width: 10px; height: 10px; } #two{ left: 100px; top: 18px; width: 15px; height: 15px; } #three{ left: 150px; top: 16px; width: 20px; height: 20px; } #four{ left: 200px; top: 14px; width: 24px; height: 24px; } #five{ left: 250px; top: 10px; width: 30px; height: 30px; } #color{ position: absolute; overflow: hidden; background-color: white; border: 2px solid gray; width: 330px; height: 30px; margin: 20px; padding: 10px; max-width: 30px; top: -300px; font-size: 30px; font-weight: bold; text-align: left; line-height: 28px; } #color > li{ position: absolute; border: 1px solid gray; list-style-type: none; width: 30px; height: 30px; top: 12px; border-radius: 30px; float: left; opacity: 1.0; } #blue{ left: 50px; background-color: blue; } #black{ left: 100px; background-color: black; } #red{ left: 150px; background-color: red; } #green{ left: 200px; background-color: green; } #yellow{ left: 250px; background-color: yellow; } #white{ left: 300px; background-color: white; } #fig_c{ position: absolute; width: 54px; height: 54px; top: -180px; left: 20px; margin: 0; padding: 0; } #circle{ position: absolute; border: 2px solid black; list-style-type: none; width: 30px; height: 30px; padding: 10px; font-size: 30px; font-weight: bold; line-height: 30px; text-align: center; color: white; background-color: red; } #circle_fill{ position: absolute; border: 2px solid black; list-style-type: none; width: 30px; height: 30px; padding: 10px; font-size: 30px; font-weight: bold; line-height: 30px; text-align: center; color: white; background-color: red; } #fig_s{ position: absolute; width: 54px; height: 54px; top: -70px; left: 20px; margin: 0px; padding: 0; } #square{ position: absolute; border: 2px solid black; width: 30px; height: 30px; padding: 10px; font-size: 30px; font-weight: bold; line-height: 30px; text-align: center; color: white; background-color: red; list-style-type: none; } #square_fill{ position: absolute; border: 2px solid black; width: 30px; height: 30px; padding: 10px; font-size: 30px; font-weight: bold; line-height: 30px; text-align: center; color: white; background-color: red; list-style-type: none; }
今回はCanvasでお絵かきサイトを作成するのがメインなので、見た目や構成は同じにする必要はありません。
自分で作成していて、分かりやすい見た目にすると良いと思います。
これでお絵かきサイトの下準備は完成しました。
続いて、javascriptを使って、実際にボタンが動いたり絵を描けるようにしていきましょう。
javascriptで生きたサイトを作ろう
まずはコードの紹介です。
順番に解説していきますので、参考にしてみてください。
$(function() { var p_c = $(".content"); var canvas = $("#canvas"); var colors = $(".color_name"); var color_name; var new_color; var pen_width = $(".width"); var pen_size = 1; var circle = $("#circle"); var circle_fill = $("#circle_fill"); var square = $("#square"); var square_fill = $("#square_fill"); var fig = $(".fig"); var fig_list = $(".fig_list"); var click = false; var c_click = false; var c_fill_click = false; var s_click = false; var s_fill_click = false; var draw_c = false; var draw_c_fill = false; var draw_s = false; var draw_s_fill = false; var mouseX_S = 0; var mouseX_E = 0; var mouseY_S = 0; var mouseY_E = 0; var can = canvas.get(0); var ctx = can.getContext("2d"); var image; var th; //.contentにマウスが乗った時の処理 p_c.hover( //マウスが乗った時 function() { $(this).animate({ maxWidth: 330 }, { duration: 300 }); }, //マウスが離れた時 function() { $(this).animate({ maxWidth: 30 }); } ) //.figにマウスが乗った時の処理 fig.hover( //マウスが乗った時 function() { th = $(this).attr("id"); $("#" + th).animate({ width: 125 }, { duration: 10 }); $("#" + th + " > li").eq(0).animate({ left: 70 }); }, //マウスが離れた時 function() { th = $(this).attr("id"); $("#" + th).animate({ width: 54 }); $("#" + th + " > li").eq(0).animate({ left: 0 }); } ) //.widthがクリックされた時の処理 pen_width.click(function() { pen_size = $(this).index() + 1; //クリックされた要素を薄くする if ($(this).css("opacity") >= 0.6) { $(this).css("opacity", "0.5"); //クリックされた要素以外のopacityを元に戻す pen_width.not($(this)).css("opacity", "1.0"); } }); //colorsがクリックされた時の処理 colors.click(function() { color_name = $(this).attr("id"); //クリックされた色を取得 new_color = color_name; if ($(this).css("opacity") >= 0.6) { $(this).css("opacity", "0.5"); colors.not($(this)).css("opacity", "1.0"); } }); //circleがクリックされた時の処理 circle.click(function() { if (circle.css("opacity") >= 0.6) { circle.css("opacity", "0.5"); fig_list.not($(this)).css("opacity", "1.0"); c_click = true; c_fill_click = false; s_click = false; s_fill_click = false; } else { circle.css("opacity", "1.0"); c_click = false; } }); //circle_fillがクリックされた時の処理 circle_fill.click(function() { if (circle_fill.css("opacity") >= 0.6) { circle_fill.css("opacity", "0.5"); fig_list.not($(this)).css("opacity", "1.0"); c_click = false; c_fill_click = true; s_click = false; s_fill_click = false; } else { circle_fill.css("opacity", "1.0"); c_fill_click = false; } }); //squareがクリックされた時の処理 square.click(function() { if (square.css("opacity") >= 0.6) { square.css("opacity", "0.5"); fig_list.not($(this)).css("opacity", "1.0"); c_click = false; c_fill_click = false; s_click = true; s_fill_click = false; } else { square.css("opacity", "1.0"); s_click = false; } }); //square_fillがクリックされた時の処理 square_fill.click(function() { if (square_fill.css("opacity") >= 0.6) { square_fill.css("opacity", "0.5"); fig_list.not($(this)).css("opacity", "1.0"); c_click = false; c_fill_click = false; s_click = false; s_fill_click = true; } else { square_fill.css("opacity", "1.0"); s_fill_click = false; } }); //canvasに描けるようにする canvas.on("mousemove", function(e) { //相対的なマウスの位置を取得 var rect = e.target.getBoundingClientRect(); mouseX_S = e.clientX - rect.left; mouseY_S = e.clientY - rect.top; //描画方法の設定 ctx.strokeStyle = new_color; ctx.fillStyle = new_color; ctx.beginPath(); ctx.lineWidth = pen_size; //ペンが選択されている場合 if (click) { ctx.moveTo(mouseX_S, mouseY_S); ctx.lineTo(mouseX_E, mouseY_E); ctx.stroke(); mouseX_E = mouseX_S; mouseY_E = mouseY_S; } //図形が選択されている場合 //丸 if (draw_c) { //保存した状態を描画 ctx.putImageData(image, 0, 0); ctx.arc(mouseX_E, mouseY_E, Math.abs(((mouseX_S - mouseX_E) + (mouseY_S - mouseY_E)) / 2), 0, 2 * Math.PI, false); ctx.stroke(); } else if (draw_c_fill) { ctx.putImageData(image, 0, 0); ctx.arc(mouseX_E, mouseY_E, Math.abs(((mouseX_S - mouseX_E) + (mouseY_S - mouseY_E)) / 2), 0, 2 * Math.PI, false); ctx.fill(); } //四角 else if (draw_s) { ctx.putImageData(image, 0, 0); ctx.moveTo(mouseX_S, mouseY_S); ctx.lineTo(mouseX_E, mouseY_S); ctx.lineTo(mouseX_E, mouseY_E); ctx.lineTo(mouseX_S, mouseY_E); ctx.closePath(); ctx.stroke(); } else if (draw_s_fill) { ctx.putImageData(image, 0, 0); ctx.moveTo(mouseX_S, mouseY_S); ctx.lineTo(mouseX_E, mouseY_S); ctx.lineTo(mouseX_E, mouseY_E); ctx.lineTo(mouseX_S, mouseY_E); ctx.closePath(); ctx.fill(); } }); canvas.on("mousedown", function(e) { //現在の状態を保存 image = ctx.getImageData(0, 0, can.width, can.height); if (!c_click && !s_click && !c_fill_click && !s_fill_click) { click = true; } else if (c_click) { draw_c = true; } else if (c_fill_click) { draw_c_fill = true; } else if (s_click) { draw_s = true; } else if (s_fill_click) { draw_s_fill = true; } mouseX_E = mouseX_S; mouseY_E = mouseY_S; }); canvas.on("mouseup", function(e) { click = false; draw_c = false; draw_c_fill = false; draw_s = false; draw_s_fill = false; }); //canvasの外でマウスを離した時の処理 $(document).on("mouseup", function() { click = false; draw_c = false; draw_c_fill = false; draw_s = false; draw_s_fill = false; }); });
今回はこのようなコードにしてみました。
では解説していきます。
要素を取得
まずは変数ですが、これらは覚えやすい名前で自由に変更しておいてください。
jQueryでは「$(“クラス (id) 名”)」で要素を取得することができます。
クラスの場合は「.」idの場合は「#」を頭につけます。これはcssの時と同じですね。
p_c.hover( )
//.contentにマウスが乗った時の処理 p_c.hover( //マウスが乗った時 function() { $(this).animate({ maxWidth: 330 }, { duration: 300 }); }, //マウスが離れた時 function() { $(this).animate({ maxWidth: 30 }); } )
このコードでは「ペンの太さを変更するボタン」と「色を変更するボタン」の上にマウスが乗った時に呼ばれるコードになっています。
コードの中に「function」が2つありますが、1つ目のfunctionで「マウスが乗った時」、2つめで「マウスが離れた時」を判定できます。
今回は「$(this)」で取得したボタンの「max-width」をアニメーションで変化させることで、ボタンが現れるようにしています。
fig.hover( )
続いて、図形にマウスが乗った時に呼ばれるコードになります。
//.figにマウスが乗った時の処理 fig.hover( //マウスが乗った時 function() { th = $(this).attr("id"); $("#" + th).animate({ width: 125 }, { duration: 10 }); $("#" + th + " > li").eq(0).animate({ left: 70 }); }, //マウスが離れた時 function() { th = $(this).attr("id"); $("#" + th).animate({ width: 54 }); $("#" + th + " > li").eq(0).animate({ left: 0 }); } )
先ほどのコードと似たコードになっていますが、「$(this).attr(“id”)」に注目してみてください。
このコードは「$(this)の(“id”)を取得する」というものになっています。
ここで取得したidを持つ要素の「width (横幅)」を広げ、取得したidの中の要素の「left」を大きくしています。
こちらも先ほどと同じように、マウスが乗った時とマウスが離れた時に呼ばれるようになっているのがわかりますね。
pen_width.click(function( ))
先ほどは「マウスが乗った時」でしたが、今度は「クリックされた時」に呼ばれるコードです。
ペンの太さを変更するボタンがクリックされた場合、そのボタンだけ透明度を少し下げるようなコードになっています。
$(this).index( ) + 1;
はクリックされた要素が何番目かを判定するコードなのですが、0スタートなので「+1」しています。
また、「pen_width.not($(this)).css( )」というコードがありますが、not( )はそれ以外を取得するものです。 今回の場合だと「$(this)」つまり、クリックされたもの以外の透明度を元に戻すことで、同時に複数選択されるのを防いでいます。
colors.click(function( ))
こちらのコードは、前に紹介したペンの太さを変更するコードと同じ内容になっていますので、省略します。
circle.click(function( ))
ここまで理解した人なら予想がつくと思いますが、このコードは「外枠のみの丸」がクリックされた時に呼ばれるものです。
こちらのコードでも、クリックされたら透明度を下げるようにしているのですが、今回はどの図形が選択されているかを判定しています。
また、今選択している図形以外の図形を選択した場合は、それ以外の図形を元に戻さないと同時に複数の図形が描画されてしまいますね。
同じようにして「circle_fill」「square」「square_fill」も記述してください。
それぞれのコードで「true」「false」を間違えないようにしましょう。
canvas.on("mousemove", function(e){ })
いよいよ実際に描画するためのコードを記述していきます。
まずはcanvas.on( )で、canvas内で発生したイベントを取得しましょう。
「getBoundingClientRect( )」を使って、canvasの値を取得しておきます。
「clientX」や「clientY」はマウスの座標を取得できるのですが、その値からcanvasの値を引くことで「Canvas内の相対的な座標」を取得しています。
お絵かきツールでは、現在の座標取得がかなり重要になってくるので、覚えておきましょう。
ctx.strokeStyle = new_color; ctx.fillStyle = new_color; ctx.beginPath(); ctx.lineWidth = pen_size;
これらは、描画する絵の設定になります。
- strokeStyle —> 線や枠の色
- filStyle —> 塗りつぶした図形の色
- beginPath —> 描画する座標指定を開始する。
- lineWidth —> 線や枠の太さ
このようにして、描画する色や太さを変更しているというわけですね。
続いて、描画する座標を指定するのですが、今回の場合「ペン」なのか「図形」なのかを判定した後で指定します。
ctx.moveTo(mouseX_S, mouseY_S); ctx.lineTo(mouseX_E, mouseY_E); ctx.stroke();
moveTo( ) —> 書き始めの座標 (x座標, y座標) lineTo( ) —> moveToを起点としたlineToまでの直線(x座標, y座標) stroke( ) —> 描画。
このようにすることで、moveToからlineToまでの直線を引くことができます。
ここでは「ペン」をイメージした描画なので、moveToからlineToまでの直線を描画した後、mouseの座標をリセットしています。 このようにすることで、クリックしている間だけペンを使うことができます。
もしmouseX_E = mouseX_Sを書かなかった場合は、lineToの座標から線が描画されるようになってしまいます。
続いて丸の座標指定です。
丸だけではなく四角の時にも出てくるのですが「putImageData( )」というコードがあります。このコードは保存したイメージを描画するというもので、getImageData( )を組み合わせて使います。
getImageData( ) はまたあとで出てくるのでその時説明します。
ちなみに「putImageData(保存したイメージ, x座標, y座標)」です。
丸を描画するためには「arc( )」というコードを使います。
こちらの記事でより詳しく紹介しているのですが
arc(x座標, y座標, 半径, 開始点, 終了点, 時計回りかどうか); d
で指定しています。
今回の場合だと、
- x座標とy座標 —> マウスの座標
- 半径 —> ((x座標の増加値 + y座標の増加値) / 2)の絶対値
- 開始点 —> 0
- 終了点 —> 2 * π
- 時計回りか —> 時計回り
のように丸の値を指定しています。
最後に、指定した値をもとに「stroke( )」で描画します。
塗りつぶした丸の場合は「fill( )」になりますね。
最後に四角の値を指定しましょう。
まずは、丸の時と同じように「putImageData( )」で保存した状態を描画します。
次に「moveTo」と「たくさんのlineTo」があるので確認してください。
このコードは「moveTo -> lineTo(1) -> lineTo(2) -> lineTo(3) -> closePath( )」の順番で座標を指定していると考えてください。
これらの座標を通る形で、直線や塗りつぶしの図形を描画します。
なぜこのような座標の値になるのかは、実際に書いてみると分かりやすいかもしれませんね。
そして「closePath( )」ですが、これは読んだ通り「閉じる」パスだと考えてみてください。
例えば「コ」の字の座標があったとして、lineToをもう一つ追加してmoveToと同じ値にすれば四角になるのですが、これだと同時に他のlineToが使えません。
そこで、closePathを使えば、moveTo(起点)とlineTo(終点)を他と干渉せずに描画できるんです。
で、最後に「stroke( )」なり「fill( )」を使って、描画を実行しています。
canvas.on("mousedown", function(e){ })
canvas内で「マウスがクリックされた」タイミングで呼び出されます。
先ほど登場した「image」に現在の状態を保存するために
「getImageData(x座標, y座標, 横幅, 高さ)」
で保存する範囲を指定します。
今回の場合だと、canvas全体を保存したいので、「ctx.getImageData(0, 0, can.width, can.height);」になりました。
続いて、現在どの描画方法が選択されているのかを判定します。
この結果によって描画するのが「図形」なのか「線」なのかを切り替えています。
canvas.on("mouseup", function(e) { })
canvas内でクリックが離された時に呼び出されます。
クリックが離れている状態では、描画する必要がないのですべてに「false」を与えます。
$(document).on("mouseup", function( ) { })
先ほどのコードと似ているのですが、こちらはcanvasの内側か外側かにかかわらず、クリックが離された時に呼ばれます。
このコードを記述しておかないと、canvas内でクリックしたままcanvasの外に出た時にクリックを話した場合、描画を終了できません。
つまり、クリックしていないのに、線が描画され続けたり、図形の大きさが指定できなかったりします。
よく考えたら、このコードを使うのであれば、先ほどのcanvas.on(“mouseup”,function( ){}) は必要なくなるかもしれませんね。
確認
ここまで完成したら、すでにcanvasに絵が描けるようになっているはずです。
試してみてください。
今回作成した機能を使って、簡単な絵を描いてみました。
まとめ
今回紹介した方法で「サイトにお絵かきできる」枠を作ることができるようになりました。
今回のコードを応用すれば、お絵かきで遊ぶサイトなんかも作れるようになるかもしれませんね。
ぜひ参考にしてみてください。
ではまた。