HTML5のcanvas内の複数の画像をドラッグ＆ドロップさせてみる


## 概説
「[HTML5のcanvas内に複数の画像を任意の順序で表示する](http://qiita.com/akase244/items/9f3c60219f903254a6e2)」で描画したcanvas内の画像をドラッグ＆ドロップしたい欲が湧いたので、コードを書いてみました。

ドラッグ＆ドロップは通常のDOMであれば、HTML5のDrag & Drop APIを利用することで簡単に対応可能です。
しかし、canvasに一度描いてしまったオブジェクトは、DOMとして操作することができないため、座標を考慮した地味な実装が必要になります。

以下のようなサイトを参考にしながら、画像の重なり順と当たり判定を考えてやってみたところ、自分が「いかに座標に弱いか」ということがよく分かりました。。。

 - [A Gentle Introduction to Making HTML5 Canvas Interactive](http://simonsarris.com/blog/510-making-html5-canvas-useful)
 - [dragging and resizing an image on html5 canvas](http://stackoverflow.com/questions/19100009/dragging-and-resizing-an-image-on-html5-canvas)

「[KineticJS](http://kineticjs.com/)」などのライブラリを使うことで、もっと簡単に実現できるようですが、今回は素のJavaScriptだけで実装することを心がけてみてます。

```js:JavaScript
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
(function() {
    var canvas  = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    var isDragging = false;
    var dragTarget = null; // ドラッグ対象の画像の添え字

    var srcs = [
        '1.png',
        '2.png',
        '3.png',
        '4.png',
    ];
    var images = [];
    for (var i in srcs) {
        images[i] = new Image();
        images[i].src = srcs[i];
    }

    var loadedCount = 0;
    for (var i in images) {
        images[i].addEventListener('load', function() {
            if (++loadedCount == images.length) {
                var x = 0;
                var y = 0;
                var w = 150;
                var h = 100;
                for (var j in images) {
                    // 画像を描画した時の情報を記憶（Imageのプロパティに突っ込むのはちょっと反則かもだけど）
                    images[j].drawOffsetX = x;
                    images[j].drawOffsetY = y;
                    images[j].drawWidth   = w;
                    images[j].drawHeight  = h;

                    // 画像を描画
                    context.drawImage(images[j], x, y, w, h);
                    x += 50;
                    y += 70;
                }
            }
        }, false);
    }

    // ドラッグ開始
    var mouseDown = function(e) {
        // ドラッグ開始位置
        var posX = parseInt(e.clientX - canvas.offsetLeft);
        var posY = parseInt(e.clientY - canvas.offsetTop);

        for (var i = images.length - 1; i >= 0; i--) {
            // 当たり判定（ドラッグした位置が画像の範囲内に収まっているか）
            if (posX >= images[i].drawOffsetX &&
                posX <= (images[i].drawOffsetX + images[i].drawWidth) &&
                posY >= images[i].drawOffsetY &&
                posY <= (images[i].drawOffsetY + images[i].drawHeight)
            ) {
              dragTarget = i;
              isDragging = true;
              break;
            }
        }
    }

    // ドラッグ終了
    var mouseUp = function(e) {
        isDragging = false;
    };

    // canvasの枠から外れた
    var mouseOut = function(e) {
        // canvas外にマウスカーソルが移動した場合に、ドラッグ終了としたい場合はコメントインする
        // mouseUp(e);
    }

    // ドラッグ中
    var mouseMove = function(e) {
        // ドラッグ終了位置
        var posX = parseInt(e.clientX - canvas.offsetLeft);
        var posY = parseInt(e.clientY - canvas.offsetTop);

        if (isDragging) {
            // canvas内を一旦クリア
            context.clearRect(0, 0, canvas.width, canvas.height);

            var x = 0;
            var y = 0;
            var w = 150;
            var h = 100;
            for (var i in images) {
                if (i == dragTarget) {
                    x = posX - images[i].drawWidth / 2;
                    y = posY - images[i].drawHeight / 2;

                    // ドラッグが終了した時の情報を記憶
                    images[i].drawOffsetX = x;
                    images[i].drawOffsetY = y;
                } else {
                    x = images[i].drawOffsetX;
                    y = images[i].drawOffsetY;
                }
                w = images[i].drawWidth;
                h = images[i].drawHeight;

                // 画像を描画
                context.drawImage(images[i], x, y, w, h);
            }
        }
    };

    // canvasにイベント登録
    canvas.addEventListener('mousedown', function(e){mouseDown(e);}, false);
    canvas.addEventListener('mousemove', function(e){mouseMove(e);}, false);
    canvas.addEventListener('mouseup',   function(e){mouseUp(e);},   false);
    canvas.addEventListener('mouseout',  function(e){mouseOut(e);},  false);
})();
</script>
</body>
```

---

#### このようにcanvas内に並んでいる画像が。。。。
![ドラッグ＆ドロップ前](https://qiita-image-store.s3.amazonaws.com/0/23093/0755c559-aab0-c8b5-4c9f-f9134add0ae3.png)

---

#### 重なり順を維持した状態でドラッグ＆ドロップができるようになりました。
![ドラッグ＆ドロップ前](https://qiita-image-store.s3.amazonaws.com/0/23093/6f9cecfa-efcd-8d3b-7fc2-ce085adbd0a7.png)