Drag and drop w html5 canvas

W niniejszym artykule zajmę się implementacją funkcjonalności drag and drop w html5 canvas. Zaczniemy od stworzenia dokumentu html z elementem canvas. Pomiędzy znacznikami script zawrzemy cały skrypt.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Drag and drop - HTML5 Canvas</title>
</head>
<body>
<div style="top: 50px; text-align:center">
<canvas id="canvas" width="600" height="450" style="border:1px solid black;"></canvas>
</div>
<script type="text/javascript">
</script>
</body>
</html>
Na początku inicjalizujemy kilka zmiennych i tworzymy funkcję (createCircles), która stworzy losowe kółka. Kółka będziemy przechowywać w tablicy circles. W każdym przejściu pętli tworzymy kółko o losowym promieniu, położeniu, kolorze i wrzucamy je do tablicy z kółkami.
var canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d");
var circles = [], drag = false, mouseX, mouseY, dragHoldX, dragHoldY;

function createCircles()
{
	var i;
	for (i=0; i < 6; i++) 
	{
		var r = 15 + Math.floor(Math.random()*25);
		var x = Math.random()*(canvas.width - r);
		var y = Math.random()*(canvas.height - r);
		var color = "rgb(" + Math.floor(Math.random()*255) + "," + Math.floor(Math.random()*255) + "," + Math.floor(Math.random()*255) +")";
		circles.push({x:x,y:y,r:r,color:color});
	}
}
createCircles();
Poniższa funkcja rysuje nasze kółka w canvasie.
function drawCircles()
{
	var i, len = circles.length;
	for(i=0;i<len;i++)
	{
		ctx.fillStyle = circles[i].color;
		ctx.beginPath();
		ctx.arc(circles[i].x, circles[i].y, circles[i].r, 0, 2*Math.PI, false);
		ctx.fill();
	}
}
drawCircles();
Teraz stworzymy trzy funkcje odpowiedzialne za obsługę zdarzeń (mousedown, mousemove i mouseup). Na końcu doczepiamy nasze zdarzenie mousedown do canvasa. Funkcja mouseDown ustala współrzędne kliknięcia na elemencie canvas, następnie przechodzi przez tablicę kółek. Jeśli funkcja inCircle zwróci true ustawiamy zmienną drag na true tzn., że można zacząć przeciąganie (mousemove). Za zdarzenia przeciągania odpowiada funkcja mouseMove. Działa to w ten sposób, że zmieniamy pozycję określonego kółka (na podstawie dragIndex), czyścimy canvasa i rysujemy kółka jeszcze raz. Funkcja mouseUp odpowiada za zdarzenie mouseup (zakończenie przeciągania).
function mouseDown(e)
{
	var i, len = circles.length;
	var bRect = canvas.getBoundingClientRect();
	mouseX = (e.clientX - bRect.left);
	mouseY = (e.clientY - bRect.top);
	for (i=0; i < len; i++) {
		if(inCircle(circles[i], mouseX, mouseY)) 
		{
			drag = true;
			dragHoldX = mouseX - circles[i].x;
			dragHoldY = mouseY - circles[i].y;
			dragIndex = i;
		}
	}
	if (drag) 
	{
		window.addEventListener("mousemove", mouseMove, false);
	}
	canvas.removeEventListener("mousedown", mouseDown, false);
	window.addEventListener("mouseup", mouseUp, false);
	return false;		
}
function mouseMove(e)
{
	var posX, posY;
	var radius = circles[dragIndex].r;
	var minX = radius;
	var maxX = canvas.width - radius;
	var minY = radius;
	var maxY = canvas.height - radius;

	var bRect = canvas.getBoundingClientRect();
	mouseX = (e.clientX - bRect.left);
	mouseY = (e.clientY - bRect.top);
	
	posX = mouseX - dragHoldX;
	posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
	posY = mouseY - dragHoldY;
	posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);
	
	circles[dragIndex].x = posX;
	circles[dragIndex].y = posY;
	
	ctx.clearRect(0,0,canvas.width, canvas.height);
	drawCircles();
}
function mouseUp()
{
	canvas.addEventListener("mousedown", mouseDown, false);
	window.removeEventListener("mouseup", mouseUp, false);
	if (drag) 
	{
		drag = false;
		window.removeEventListener("mousemove", mouseMove, false);
	}
}
canvas.addEventListener("mousedown", mouseDown, false);
Poniższa funkcja sprawdza czy kliknęliśmy wewnątrz kółka. Ustala to na podstawie koloru klikniętego piksela. Jeśli istnieje kółko w tablicy circles o takim kolorze to znaczy, że zostało kliknięte.
function inCircle(circle,mx,my)
{
	var imageData = ctx.getImageData(mx, my, 1, 1), index = (mx + my * imageData.width) * 4;
	if (imageData.data[3] > 0 && circle.color=="rgb(" + imageData.data[0] + "," + imageData.data[1] + "," + imageData.data[2] +")") 
	{
		return true;
	}
	else
	{
		return false;
	}
}
Tutaj przykład w działaniu.