Top

canvas を使ってお絵かきツールを作る方法と仕組み

JavaScript で HTML5 より追加された canvas を操作してお絵かきツールを作ってみます。

すでに、多くのライブラリがあるため何番煎じであるかわからないですが、仕組み的な事を書いていきたいと思います。

お絵かきツール

ライブラリ自体の中身を読んだりはしてないので、全てのライブラリが同じ仕組みかどうかまではわかりません。私が思いついた方法という事になります。

注意:現在スマホでは絵を描くことが出来ません。PC でご覧ください。

canvasお絵かきツールデモ

スポンサーリンク

解説

灰色のキャンバスの上でマウスを左クリックすると絵を描くことが出来ます。下のボタンで「透明度」「色」「サイズ」などを変えることが出来、「消」消しゴムと「×」全消し機能も付けました。

以下、コードと解説です。canvas についての細かい説明は省き、お絵かきツールを作るためのコードを中心に説明します。

JavaScript

(function() {
    //HTML上の canvas タグを取得
    var canvas = document.getElementById('myCanvas');
 
    //レスポンシブ対応 画面サイズでキャンバスサイズを調整します
    if (screen.width < 860) {
        canvas.width = 700 * screen.width / 860;
        canvas.height = 400 * screen.width / 860;
    }
 
    //キャンバスの背景カラーを決定。 fillRectは長方形に塗るメソッド
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.fillStyle = "#f5f5f5";
    ctx.fillRect(0, 0, 700, 400);
 
    //初期値(サイズ、色、アルファ値)の決定
    var defosize = 7;
    var defocolor = "#555555";
    var defoalpha = 1.0;
 
    //マウス継続値の初期値、ここがポイント
    var mouseX = "";
    var mouseY = "";
 
    //各イベントに紐づけ
    canvas.addEventListener('mousemove', onMove, false);
    canvas.addEventListener('mousedown', onClick, false);
    canvas.addEventListener('mouseup', drawEnd, false);
    canvas.addEventListener('mouseout', drawEnd, false);
 
    //マウス動いていて、かつ左クリック時に発火。
    function onMove(e) {
        if (e.buttons === 1 || e.witch === 1) {
            var rect = e.target.getBoundingClientRect();
            var X = ~~(e.clientX - rect.left);
            var Y = ~~(e.clientY - rect.top);
            //draw 関数にマウスの位置を渡す
            draw(X, Y);
        };
    };
 
    //マウスが左クリックされると発火。
    function onClick(e) {
        if (e.button === 0) {
            var rect = e.target.getBoundingClientRect();
            var X = ~~(e.clientX - rect.left);
            var Y = ~~(e.clientY - rect.top);
            //draw 関数にマウスの位置を渡す
            draw(X, Y);
        }
    };
 
    //渡されたマウス位置を元に直線を描く関数
    function draw(X, Y) {
        ctx.beginPath();
        ctx.globalAlpha = defoalpha;
        //マウス継続値によって場合分け、直線の moveTo(スタート地点)を決定
        if (mouseX === "") {
            //継続値が初期値の場合は、現在のマウス位置をスタート位置とする
            ctx.moveTo(X, Y);
        } else {
            //継続値が初期値ではない場合は、前回のゴール位置を次のスタート位置とする
            ctx.moveTo(mouseX, mouseY);
        }
        //lineTo(ゴール地点)の決定、現在のマウス位置をゴール地点とする
        ctx.lineTo(X, Y);
        //直線の角を「丸」、サイズと色を決める
        ctx.lineCap = "round";
        ctx.lineWidth = defosize * 2;
        ctx.strokeStyle = defocolor;
        ctx.stroke();
        //マウス継続値に現在のマウス位置、つまりゴール位置を代入
        mouseX = X;
        mouseY = Y;
    };
 
    //左クリック終了、またはマウスが領域から外れた際、継続値を初期値に戻す
    function drawEnd() {
        mouseX = "";
        mouseY = "";
    }
 
    //メニューのアイコン関係
    var menuIcon = document.getElementsByClassName("menuicon");
    for (i = 0; i < menuIcon.length; i++) {
        menuIcon[i].addEventListener("click", canvasMenu, false)
    }
 
    //メニューボタン管理
    function canvasMenu() {
        //id 値によって場合分け
        var thisId = this.id;
        if (thisId.indexOf("size") + 1) {
            defosize = ~~this.id.slice(4, this.id.length);
        }
        if (thisId.indexOf("color") + 1) {
            defocolor = "#" + this.id.slice(5, this.id.length);
        }
        if (thisId.indexOf("alpha") + 1) {
            defoalpha = (~~this.id.slice(5, this.id.length)) / 10;
        }
        if (thisId.indexOf("clear") + 1) {
            //全消しボタン、OKされた場合は fillRect 長方形で覆います
            if (confirm("すべて消去しますか?")) {
                ctx.beginPath();
                ctx.fillStyle = "#f5f5f5";
                ctx.globalAlpha = 1.0;
                ctx.fillRect(0, 0, 700, 400);
            }
        }
    }
})();

HTML

<canvas id="myCanvas" width="700" height="400"></canvas>
<div id="canvasmenu">
    <div class="menuicon" id="clear">×</div>
    <div class="menuicon" id="colorf5f5f5">消</div>
    <div class="menuicon" id="size21">21</div>
    <div class="menuicon" id="size14">14</div>
    <div class="menuicon" id="size7">7</div>
    <div class="menuicon" id="size3">3</div>
    <div class="menuicon" id="colore91e63" style="background-color:#e91e63"></div>
    <div class="menuicon" id="colorffeb3b" style="background-color:#ffeb3b"></div>
    <div class="menuicon" id="colorff4801" style="background-color:#ff4801"></div>
    <div class="menuicon" id="color536dfe" style="background-color:#536dfe"></div>
    <div class="menuicon" id="color009688" style="background-color:#009688"></div>
    <div class="menuicon" id="color00e676" style="background-color:#00e676"></div>
    <div class="menuicon" id="color555555" style="background-color:#555555"></div>
    <div class="menuicon" id="alpha10" style="background-color:rgba(255,72,1, 1.0)">10</div>
    <div class="menuicon" id="alpha6" style="background-color:rgba(255,72,1, 0.6)">6</div>
    <div class="menuicon" id="alpha3" style="background-color:rgba(255,72,1, 0.3)">3</div>
    <div class="menuicon" id="alpha1" style="background-color:rgba(255,72,1, 0.1)">1</div>
</div>

マウス継続値

JavaScript コード内での最大のポイントは「マウス継続値」の設定です。

初期値は数字以外であれば何でも構わないので、空白文字列にしておきます。mouseMove はマウスが動いていると発火するのですが、あまり細かくマウスの位置を返す訳ではありません。飛び飛びのマウス位置が返ってきてしまうため、その飛び飛びの値を直線でつなぐことで滑らかに繋がってるように見せます。

継続値は左クリックが領域内で押されてる限り続くようになっており、直線のゴール地点の位置が次の直線のスタート位置になるよう継続していくため、「マウス継続値」と勝手に名付けました。

継続値は左クリックが押されている限り存在している形で、左クリックが終了すると初期値に戻るようにしています。継続値が初期値「""」だった場合は、スタート地点とゴール地点が同じ値にあるため、ただ「点」を打つような挙動になります。

メニュー関連

ボタンが押されると色々変更されます。場合分けは少し雑ですが、id の値を indexOf で検索し返ってくる値で分けています。id に細工を入れており、id の値を取得して例えば色であれば「colore91e63」の「e91e63」を取得、デフォルト値自体を変更という形をとっています。

canvas そのものと関係ないため深くは説明しませんが、メニュー等の一元管理には結構便利な方法です。

マウスボタン関連

マウスボタンに関しては現在は js の規格がほぼ統一化されているため書きやすくなっています。

ただ、動いているときのマウスボタン(mousemove)と単なるクリック(mousedown)時のボタンの番号(?)が変わってくるため少しめんどくさいです。詳しくは下記サイトをご参考ください。

参考:[JavaScript] mousemoveイベントで押下状態取得
参考:マウスボタンの押下状態を調べる

少し情報が古いのですが、いろいろブラウザをチェックしたところ、最新ブラウザであればコード上の分け方で大丈夫なようです。

まとめ

私は全く絵が描けないので、これと言って活用法はありません・・・

マウス継続値を導入することで、実はかなりコードをシンプルに書くことが出来ています。また、「飛び飛びの値を直線でつなぐ」というアイデアで線が途切れることなく描くことが出来ています。ほかにも色々機能を付けることが出来るので、それも考えていけたらなー思ってます。

あと、canvas タグの性質なのですが、ブラウザによってはお絵かきツール上で右クリックすると、「画像を保存」を選択して保存することが出来ます。そのまま保存することが出来るというところが面白いですね。