アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

【HTML5】Canvasでお絵かきしてみた(前編)

こんにちは、橋本です。

今回はHTML5シリーズ第二弾ということで、canvasを使ってお絵かきしてみました。
今回は前編で、基本的な機能について触れてみたいと思います。

さっそくサンプルコードを以下に。

canvas_sample.html


<html lang="ja">
<head>
	<meta charset="UTF-8">
    <script src="js/canvas.js" type="text/javascript"></script>
    <link media="all" rel="stylesheet" href="css/main.css" type="text/css" />
    <title>canvas test</title>
</head>
<body>
    <div id="container">
        <div id="leftContainer" class="clearfix">
            <canvas id="layer0" class="canvas" style="position: absolute; top: 0; left: 0; border: 10px solid #dddddd;" width="900px" height="600px"></canvas>
        </div>
        <div id="rightContainer">
            <div id="controler">
                <section id="color">
                    <ul>
                        <li>
                            <label for="red">R:</label>
                            <input id="red" class="colorBar" type="range" min="0" max="255">
                        </li>
                        <li>
                            <label for="green">G:</label>
                            <input id="green" class="colorBar" type="range" min="0" max="255">
                        </li>
                        <li>
                            <label for="blue">B:</label>
                            <input id="blue" class="colorBar" type="range" min="0" max="255">
                        </li>
                        <li>
                            <label for="alpha">A:</label>
                            <input id="alpha" class="colorBar" type="range" min="0" max="100">
                        </li>
                        <li>
                            <canvas id="colorSample" height="30px" width="30px"></canvas>
                            <input id="colorText" type="text" value="" readonly="true">
                        </li>
                    </ul>
                </section>
                <section id="line">
                    <ul>
                        <li>
                            <label for="lineWidthRange">line width:</label>
                            <input id="lineWidthRange" type="range" min="0" max="50">
                        </li>
                        <li>
                            <canvas id="lineSample" width="50" height="50"></canvas>
                        </li>
                    </ul>
                </section>
                <section id="other">
                    <ul>
                        <li>
                            <label for="erase">erase mode:</label>
                            <input id="erase" type="checkbox" />
                        </li>
                    </ul>
                </section>
            </div>
        </div>
    </div>
</body>
</html>


canvas.js


(function(){

  var canvas,context = null;
  var beforeX, beforeY = null;
  var isDrawing =  false;
  var selectedColor;
  var colorVars;
  var colorText;
  var colorSample;
  var erase;
  var restore;
  var lineWidthRange;
  var lineWidth;
  var lineSample;
  var isErase;

  window.addEventListener("load", function (event){
    canvas = document.getElementById("layer0");
    context = canvas.getContext("2d");

    erase = document.getElementById("erase");
    restore = document.getElementById("restore");
    lineWidthRange = document.getElementById("lineWidthRange");

    // イベントリスナーの設定
    canvas.addEventListener("mousemove", canvas_mouseMoveHandler, false);
    canvas.addEventListener("mouseup", canvas_mouseUpHandler, false);
    canvas.addEventListener("mousedown", canvas_mouseDownHandler, false);
    canvas.addEventListener("mouseout", canvas_mouseOutHandler, false);

    erase.addEventListener("click", erase_clickHandler, false);
    lineWidthRange.addEventListener("change", lineWidthRange_changeHandler, false);
    

    //controlerの設定
    colorBars = document.getElementsByClassName("colorBar");

    var obj = {};

    length = colorBars.length;
    for (var i = 0; i < length; i++)
    {
        var bar = colorBars[i];
        bar.value = bar.id == "alpha" ? 100 : 0;

        bar.addEventListener("change", colorBar_changeHandler, false);

        obj[bar.id] = bar.value;
    }

    colorSample = document.getElementById("colorSample");
    colorText = document.getElementById("colorText");

    var colorStr = convertColorObjToStr(obj);

    setColorText(colorStr);
    setSelectedColor(colorStr);

    setColorSample();

    lineWidthRange.value = 10;
    lineWidth = 10;
    lineSample = document.getElementById("lineSample");

    setLineSample();

  }, false);

  function convertColorObjToStr(obj)
  {
      return obj["red"] + ", " + obj["green"] + ", " + obj["blue"] + ", " + (obj["alpha"] / 100);
  }

  function setColorSample()
  {
      var context = colorSample.getContext("2d");

      context.beginPath();
      context.strokeStyle = selectedColor;
      context.fillStyle = selectedColor;
      context.fillRect(0, 0, colorSample.width, colorSample.height);
  }

  function setLineSample()
  {
      var context = lineSample.getContext("2d");
      // 今の状態をリセット
      context.clearRect(0, 0, lineSample.width, lineSample.height);

      context.beginPath();
      context.arc(lineSample.width / 2, lineSample.height / 2, lineWidth / 2, 0, Math.PI*2, false);

      if (isErase)
      {
          context.strokeStyle = "rgba(0, 0, 0, 1)";
          context.stroke();
      }
      else
      {
          context.strokeStyle = selectedColor;
          context.fillStyle = selectedColor;
          context.fill();
      }
  }

  function setColorText(colorStr)
  {
      colorText.value = colorStr;
  }

  function setSelectedColor(colorStr)
  {
      selectedColor = "rgba(" + colorStr + ")";
  }

  function colorBar_changeHandler(event)
  {
      var obj = {};

      length = colorBars.length;
      for (var i = 0; i < length; i++)
      {
          var bar = colorBars[i];
          obj[bar.id] = bar.value;
      };

      var colorStr = convertColorObjToStr(obj);

      setColorText(colorStr);
      setSelectedColor(colorStr);

      setColorSample();
      setLineSample();
  }

  function canvas_mouseOutHandler(event)
  {
      isDrawing = false;
  }

  function canvas_mouseDownHandler(event)
  {
      isDrawing = true;
      beforeX = event.clientX - 10;
      beforeY = event.clientY - 10;
  }

  function canvas_mouseUpHandler(event)
  {
      isDrawing = false;
  }

  function canvas_mouseMoveHandler(event)
  {
      if (!isDrawing)
      {
          return;
      }

      isErase ? eraseLine(event) : drawLine(event);
  }

  function drawLine(event)
  {
      var fixedX = event.clientX - 10;
      var fixedY = event.clientY - 10;
      
      context.beginPath();

      context.strokeStyle = selectedColor;
      context.lineWidth = lineWidth;
      context.lineCap = 'round';
      context.lineJoin = 'round';
      context.moveTo(beforeX, beforeY);
      context.lineTo(fixedX, fixedY);
      context.stroke();

      context.closePath();

      beforeX = fixedX;
      beforeY = fixedY;
  }

  function eraseLine(event)
  {
      var x = event.clientX - 10 - lineWidth / 2;
      var y = event.clientY - 10 - lineWidth / 2;

      context.clearRect(x, y, lineWidth, lineWidth);
  }

  function erase_clickHandler(event)
  {
      isErase = event.target.value;
      setLineSample();
  }

  function lineWidthRange_changeHandler(event)
  {
      lineWidth = event.target.value;
      setLineSample();
  }
  

})();


*cssは略

JSのコードが結構汚いんですが、サンプルってことで華麗にスルーしていただけると幸いです。(次回までに綺麗にしておきます;)

実際のサンプルはこちら


まず、canvasで画像を描画する際には、対象のcanvasのcontextを取得します。


    canvas = document.getElementById("layer0");
    context = canvas.getContext("2d");

contextに対して処理を行うことで、画像を描画することが出来ます。

次に基本的な線を引く処理。
線を引くためには、以下の手順を踏みます。


// 1. 線を描きますよーと宣言
context.beginPath();

// 2. 線の設定(しなくてもOK)
context.strokeStyle = selectedColor; // 線の色
context.lineWidth = lineWidth; // 線の太さ
context.lineCap = 'round'; // 線の始点、終点の形
context.lineJoin = 'round'; // 線のつなぎ目の形

// 3. 線の始点を設定
context.moveTo(beforeX, beforeY);

// 4. 線の終点を設定
context.lineTo(fixedX, fixedY);

// 5. 線を描く
context.stroke();

// 6. 線が書き終わりましたよーという宣言
context.closePath();


線の各種設定については、以下のページに詳しく載ってるので参照してください。
HTML5.jp-Canvasリファレンス

今回のサンプルでは、連続した線を引くために、線を引き終わった座標を、beforeX, beforeYに格納し、次の線を描き始めるときの始点として指定しています。

次に四角形を描く処理。
四角形を描くためには、以下の手順を踏みます。


// 1.線を描きますよーの宣言
context.beginPath();

// 2. 設定
context.strokeStyle = selectedColor; // 線の色
context.fillStyle = selectedColor; // 塗りつぶしの色

// 3. 四角形を描く
context.fillRect(0, 0, colorSample.width, colorSample.height);


上記サンプルのfillRectは、塗りつぶした四角形を描くメソッドです。
引数は、書き始めるx座標、書き始めるy座標、四角形の幅、四角形の高さとなっています。
座標は、左上が(0, 0)です。これは、canvas全体共通です。

ちなみに、枠だけの四角形を描きたいときは、strokeRect()、四角形を消したいときは、clearRect()メソッドを使います。今回のサンプルでは、消しゴムを実装するために、clearRect()メソッドを使っています。

次は円を描く処理です。
円を描くためには以下の手順。


// 1. 例のやつ
context.beginPath();

// 2. 円の形を指定
context.arc(lineSample.width / 2, lineSample.height / 2, lineWidth / 2, 0, Math.PI*2, false);

// 3. オプションを指定
context.strokeStyle = selectedColor;
context.fillStyle = selectedColor;

// 4. 描く
context.fill();


まず、arc()メソッドを使って円の設定をします。
引数は、円の中心のx座標、円の中心のy座標、円の半径、円を書き始める角度、円を書き終える角度、円を描く向きとなっています。最後の引数の円を描く向きは、trueを指定すると、反時計回り、falseを指定すると、時計回りになります。

今回は普通の円なので、最後の指定はあまり意味がないのですが、一部が欠けている円を描きたい時には、向きが重要になってくると思います。

ちなみに、枠だけの円を描きたいときは、最後のfill()をstroke()に変更すればOKです。


今回はとりあえず、ここまで。

次回後編では、グラデーション、画像の取り込みなどについて触れていきたいと思います。