概説
「HTML5のcanvas内に複数の画像を任意の順序で表示する」で描画したcanvas内の画像をドラッグ&ドロップしたい欲が湧いたので、コードを書いてみました。
ドラッグ&ドロップは通常のDOMであれば、HTML5のDrag & Drop APIを利用することで簡単に対応可能です。
しかし、canvasに一度描いてしまったオブジェクトは、DOMとして操作することができないため、座標を考慮した地味な実装が必要になります。
以下のようなサイトを参考にしながら、画像の重なり順と当たり判定を考えてやってみたところ、自分が「いかに座標に弱いか」ということがよく分かりました。。。
- A Gentle Introduction to Making HTML5 Canvas Interactive
- dragging and resizing an image on html5 canvas
「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>