# How to Drag and Drop Objects with JavaScript and HTML5 Canvas

##### 2020-10-02 23:10:03 |

## Tested On

- Linux Ubuntu 20.04
- Windows 10
- macOS Catalina

HTML5 Canvas is not just limited to producing visuals. It can produce complex interactions, as well. A good place to start is by giving the user the ability to pick up objects and drag them around the screen. This involves a bit of math for the hit detection, but I promise it won't be difficult.

Before we dive in, try playing around with the following demo. Feel free to update the values in the code editor, below. If the code looks daunting, don't worry. We explain all of the math and logic, in the next section, as well as provide you with the full source code.

Code Editor

## Drag and Drop Logic

For drag and drop functionality, the basic logic flow is as follows:

- Detect when the user presses down
- If the coordinates of the user's interaction fall within the bounds of a shape, set isDragging for target shape to true
- If the user moves the mouse/touch before releasing, update the position of all target shapes to match the mouse/touch coordinates
- If the user releases, set isDragging for all shapes to false

## How to See If the Mouse is Inside a Rectangle

When dealing with unrotated rectangles, determining if the user's mouse/touch is colliding with the shape is fairly straightforward. We just take the XY coordinates of the mouse or touch, and check to see if it's greater than the top and left sides and less than the bottom and right sides. The cursor at 3, on the x-axis, and 1 on the y-axis falls within the bounds, but the cursor at 5,1 misses.

## How to Use the Pythagorean Theorem for Circle Hit Detection

With a circle, we have a round perimeter to deal with. Using the same rectangular bounding box hit detection would be insufficient because of the gaps between the curved arc of a circle and the straight edges of a rectangle. To get pixel-perfect collision detection with round shapes, we can rely on the Pythagorean Theorem.

Given a right triangle, if you add both sides of a triangle and square them, they will equal the hypotenuse squared. So in the above example, *c ^{2} = a^{2} + b^{2}*. Why are we talking about triangles, you might be asking? Because a right triangle is formed in the space between a shape and the user's mouse, and the hypotenuse of that triangle gives us the distance between those two points. Take a look at the following diagram:

Notice that wherever the user position's the mouse/touch, a right triangle is formed. Using the pythagorean theorem, we can determine that *distance = (mouseX - circleX) ^{2} + (mouseY - circleY)^{2}*. So if the distance between the shape and the mouse is less than the distance between the center of the circle and its radius, we can conclude that the user is clicking on the circle.

## Let's Write Some JavaScript Code

Let's translate all this math into some JavaScript code. Just import it into an HTML file, with a canvas element with an ID of "canvas", and you will have drag and drop functionality.

```
var Rectangle = function(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.isDragging = false;
this.render = function(ctx) {
ctx.save();
ctx.beginPath();
ctx.rect(this.x - this.width * 0.5, this.y - this.height * 0.5, this.width, this.height);
ctx.fillStyle = '#2793ef';
ctx.fill();
ctx.restore();
}
}
var Arc = function(x, y, radius, radians) {
this.x = x;
this.y = y;
this.radius = radius;
this.radians = radians;
this.isDragging = false;
this.render = function(ctx) {
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, this.radians, false);
ctx.fillStyle = '#2793ef';
ctx.fill();
ctx.restore();
}
}
var MouseTouchTracker = function(canvas, callback){
function processEvent(evt) {
var rect = canvas.getBoundingClientRect();
var offsetTop = rect.top;
var offsetLeft = rect.left;
if (evt.touches) {
return {
x: evt.touches[0].clientX - offsetLeft,
y: evt.touches[0].clientY - offsetTop
}
} else {
return {
x: evt.clientX - offsetLeft,
y: evt.clientY - offsetTop
}
}
}
function onDown(evt) {
evt.preventDefault();
var coords = processEvent(evt);
callback('down', coords.x, coords.y);
}
function onUp(evt) {
evt.preventDefault();
callback('up');
}
function onMove(evt) {
evt.preventDefault();
var coords = processEvent(evt);
callback('move', coords.x, coords.y);
}
canvas.ontouchmove = onMove;
canvas.onmousemove = onMove;
canvas.ontouchstart = onDown;
canvas.onmousedown = onDown;
canvas.ontouchend = onUp;
canvas.onmouseup = onUp;
}
function isHit(shape, x, y) {
if (shape.constructor.name === 'Arc') {
var dx = shape.x - x;
var dy = shape.y - y;
if (dx * dx + dy * dy < shape.radius * shape.radius) {
return true
}
} else {
if (x > shape.x - shape.width * 0.5 && y > shape.y - shape.height * 0.5 && x < shape.x + shape.width - shape.width * 0.5 && y < shape.y + shape.height - shape.height * 0.5) {
return true;
}
}
return false;
}
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var startX = 0;
var startY = 0;
var rectangle = new Rectangle(50, 50, 100, 100);
rectangle.render(ctx);
var circle = new Arc(200, 140, 50, Math.PI * 2);
circle.render(ctx);
var mtt = new MouseTouchTracker(canvas,
function(evtType, x, y) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
switch(evtType) {
case 'down':
startX = x;
startY = y;
if (isHit(rectangle, x, y)) {
rectangle.isDragging = true;
}
if (isHit(circle, x, y)) {
circle.isDragging = true;
}
break;
case 'up':
rectangle.isDragging = false;
circle.isDragging = false;
break;
case 'move':
var dx = x - startX;
var dy = y - startY;
startX = x;
startY = y;
if (rectangle.isDragging) {
rectangle.x += dx;
rectangle.y += dy;
}
if (circle.isDragging) {
circle.x += dx;
circle.y += dy;
}
break;
}
circle.render(ctx);
rectangle.render(ctx);
}
);
```

Here's an explanation of the code, above.

**Lines 1-38** give us a Rectangle and Arc that we can instantiate with our desired parameters. We won't be explaining this, here, since our primary focus is drag and drop functionality.

**Lines 40-84** define all the logic for detecting user mouse/touch input and returning their coordinates.

**Lines 86-100** is where our hit detection code lives. We first check to see if the shape is an Arc. If so, we calculate the distance between the mouse and the shape (dx and dy), and see if squaring both distances is less than the circle's radius squared. If so, we can determine that the user that clicked inside the circle.

**Lines 94-96** is the hit detection for rectangular shapes. We use the same logic mentioned at the beginning of this tutorial, but we offset everything by half the rectangle's width and height because our rectangle's centerpoint is at the center, not the top left.

**Lines 119-151** is our drag functionality. We toggle the isDragging flag for each shape on mouse/touch down and up. And we update the shape's position if the user moves around the screen with their mouse pressed down.

## Conclusion

So there you have it—a tutorial explaining how to drag and drop objects with canvas. We hope you find this guide useful.

## Comments

You must log in to comment. Don't have an account? Sign up for free.

Subscribe to comments for this post

## Info