63
82

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HTML5 Canvas でドラッグ操作

Posted at

以下の記事で HTML5 Canvas に入門しました。
http://qiita.com/kyrieleison/items/a3ebf7c55295c3e7d8f0

が、どうせなら描画した要素をドラッグでぐりぐり動かすくらいは、やってみたいところです。
やってみました。

コードだけ見たい方はこちらを。
https://github.com/kyrieleison/canvas-dragging-tutorial/blob/master/index.html

キャンバスを準備する

<!DOCTYPE html>
<html>
  <head>
    <title>canvas tutorial</title>
    <style>
      #canvas {
        background: #666;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="640" height="480"></canvas>
    <script>
      // ここにコードを書いていきます
    </script>
  </body>
</html>

まずは HTML の雛形です。

ドラッグで動かすオブジェクトを初期表示する

var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var objX, objY;
var objWidth, objHeight;

function init() {
  // オブジェクトの大きさを定義
  objWidth = 50;
  objHeight = 50;

  // オブジェクトの座標を定義(キャンバスの中央に表示)
  objX = canvas.width / 2 - objWidth / 2;
  objY = canvas.height / 2 - objHeight / 2;

  // オブジェクトを描画
  drawRect();
}

function drawRect() {
  context.fillRect(objX, objY, objWidth, objHeight);
}

まずはドラッグで動かす対象となるオブジェクトを表示します。
今回は縦50px X 横50px のシンプルな正方形をキャンバスの中央に表示する init() 関数を定義します。

<body onLoad="init()">
  <canvas id="canvas" width="640" height="480"></canvas>
  <script>
    ...
  </script>
</body>

そして body タグで雑な感じに init() 関数を実行するようにします。
これでキャンバスにオブジェクトが表示されるようになりました。

マウス座標を取得し、オブジェクトかどうかを判定する

var x, y, relX, relY;
var dragging = false;

function onDown(e) {
  // キャンバスの左上端の座標を取得
  var offsetX = canvas.getBoundingClientRect().left;
  var offsetY = canvas.getBoundingClientRect().top;

  // マウスが押された座標を取得
  x = e.clientX - offsetX;
  y = e.clientY - offsetY;

  // オブジェクト上の座標かどうかを判定
  if (objX < x && (objX + objWidth) > x && objY < y && (objY + objHeight) > y) {
    dragging = true; // ドラッグ開始
    relX = objX - x;
    relY = objY - y;
  }
}

canvas.addEventListener('mousedown', onDown, false);

次に、マウスの左ボタンが押されたときに、それがオブジェクト上なのかどうかを判定します。
押されたのがオブジェクト上であればドラッグ開始(dragging = true)と見なし、オブジェクト上でなければ何もしません。

マウスが押された座標を取得するためには、canvas.getBoundingClientRect() メソッドで画面上でのキャンバスの座標を取得します。
clientX/Y プロパティで取得した画面上の座標から、キャンバス座標を減算することでキャンバス内の相対位置を取得できます。

座標は常にオブジェクトの左上を指し、右あるいは下に行くほど大きな値となるため、オブジェクト上の座標かどうかは以下のような判定条件になります。

  • objX < x: オブジェクトの左端よりも右で押された場合
  • (objX + objWidth) > x: オブジェクトの右端よりも左で押された場合
  • objY < y: オブジェクトの上端よりも下で押された場合
  • (objY + objHeight) > x: オブジェクトの下端よりも上で押された場合

これらの判定条件をクリアするとオブジェクトの上でドラッグが開始されたとみなすことができるので、dragging フラグを true にします。
また、オブジェクトとマウス座標との差分を relX/Y に格納しておきます。

ドラッグでオブジェクトを移動する

function onMove(e) {
  // キャンバスの左上端の座標を取得
  var offsetX = canvas.getBoundingClientRect().left;
  var offsetY = canvas.getBoundingClientRect().top;

  // マウスが移動した先の座標を取得
  x = e.clientX - offsetX;
  y = e.clientY - offsetY;

  // ドラッグが開始されていればオブジェクトの座標を更新して再描画
  if (dragging) {
    objX = x + relX;
    objY = y + relY;
    drawRect();
  }
}

function onUp(e) {
  dragging = false; // ドラッグ終了
}

function drawRect() {
  context.clearRect(0, 0, canvas.width, canvas.height); // キャンバスをクリア
  context.fillRect(objX, objY, objWidth, objHeight);
}

canvas.addEventListener('mousemove', onMove, false);
canvas.addEventListener('mouseup', onUp, false);

キャンバス上をドラッグしてオブジェクトを移動するには、今描画しているオブジェクトを消去して、次のマウス座標上にオブジェクトを再描画するということをします。

マウス移動を常に監視し、オブジェクト上でドラッグが開始していればその移動先のマウス座標でオブジェクトを描画します。
そして、マウスの左ボタンが離されたらドラッグを終了と見なします。

このときに、前の位置のオブジェクトがキャンバス上に残り続けてしまうため、context.clearRect() メソッドで描画する度にキャンバス全体をクリアします。

これでキャンバス上でのドラッグ移動ができました。


今回は以下の記事やコードを参考にさせていただきました。
http://rika.edu.gunma-u.ac.jp/~terasima/junk/ex07.html
http://lab.ve-lo.net/?p=312
http://qiita.com/akase244/items/b801f435e85ea67a70eb

ただ、この実装では複数のオブジェクトが描画されたときに、ドラッグする度に他のオブジェクトもクリアされてしまいます。
オブジェクトが長方形でないと判定の仕方も変わりますし、オブジェクトの重ね順についても考慮されていません。

通常の DOM 要素であれば HTML5 Drag & Drop API で比較的簡単に実装できるようですが、キャンバスだと座標計算をごりごりするコードになりそうです。
ユーザに描画をさせるような機能であれば別ですが、単にドラッグアンドドロップを実現するのであれば、キャンバスは避けた方がいいかもしれませんね。

63
82
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
82

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?