JavaScript
HTML5
canvas

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

More than 3 years have passed since last update.

概説

HTML5のcanvas内に複数の画像を任意の順序で表示する」で描画したcanvas内の画像をドラッグ&ドロップしたい欲が湧いたので、コードを書いてみました。

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

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

KineticJS」などのライブラリを使うことで、もっと簡単に実現できるようですが、今回は素のJavaScriptだけで実装することを心がけてみてます。

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内に並んでいる画像が。。。。

ドラッグ&ドロップ前


重なり順を維持した状態でドラッグ&ドロップができるようになりました。

ドラッグ&ドロップ前