Reputation: 1183
I am trying to draw resizable rectangles on a canvas element. I check the position of the mouse in relation to the rectangles on the screen then update width/ height of the triangle within which the mouse falls. So far I have been successful save for reducing element width/ height. This is because the mouse position is within range of rectangle co-ordinates.
How would I handle expansion? Here's the code
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
rect = [],
handlesSize = 4,
currentHandle = false,
drag = false,
selected = false;
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
function point(x, y) {
return {
x: x,
y: y
};
}
function collides(rects, x, y) {
var isCollision = false;
for (var i = 0, len = rects.length; i < len; i++) {
var left = rects[i].x, right = rects[i].x+rects[i].w;
var top = rects[i].y, bottom = rects[i].y+rects[i].h;
if (right >= x
&& left <= x
&& bottom >= y
&& top <= y) {
isCollision = i;
}
}
return isCollision;
}
function dist(p1, p2) {
return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}
function getHandle(e,mouse) {
var returned = false;
selected = collides(rect, e.offsetX, e.offsetY);
if(selected || selected === 0)
{
if (dist(mouse, point(rect[selected].x, rect[selected].y)) <= handlesSize) returned = 'topleft';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y)) <= handlesSize) returned = 'topright';
if (dist(mouse, point(rect[selected].x, rect[selected].y + rect[selected].h)) <= handlesSize) returned = 'bottomleft';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y + rect[selected].h)) <= handlesSize) returned = 'bottomright';
if (dist(mouse, point(rect[selected].x + rect[selected].w / 2, rect[selected].y)) <= handlesSize) returned = 'top';
if (dist(mouse, point(rect[selected].x, rect[selected].y + rect[selected].h / 2)) <= handlesSize) returned = 'left';
if (dist(mouse, point(rect[selected].x + rect[selected].w / 2, rect[selected].y + rect.h)) <= handlesSize) returned = 'bottom';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y + rect[selected].h / 2)) <= handlesSize) returned = 'right';
}
return returned;
}
function mouseDown(e) {
if (currentHandle)
{
draw();
drag = true;
}
else
{
var mousePos = point(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
rect.push({x: mousePos.x, y: mousePos.y, w: 80, h: 20});
draw();
drag = true;
selected = collides(rect, e.offsetX, e.offsetY);
}
}
function mouseUp() {
drag = false;
currentHandle = false;
draw();
}
function mouseMove(e) {
var previousHandle = currentHandle;
if (!drag) currentHandle = getHandle(e,point(e.pageX - this.offsetLeft, e.pageY - this.offsetTop));
selected = collides(rect, e.offsetX, e.offsetY);
var select = rect[selected];
if (currentHandle && drag && selected) {
var mousePos = point(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
var select = rect[selected];
switch (currentHandle) {
case 'topleft':
rect[selected].w += select.x - mousePos.x;
rect[selected].h += select.y - mousePos.y;
rect[selected].x = mousePos.x;
rect[selected].y = mousePos.y;
break;
case 'topright':
rect[selected].w = mousePos.x - rect[selected].x;
rect[selected].h += rect[selected].y - mousePos.y;
rect[selected].y = mousePos.y;
break;
case 'bottomleft':
rect[selected].w += rect[selected].x - mousePos.x;
rect[selected].x = mousePos.x;
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'bottomright':
rect[selected].w = mousePos.x - rect[selected].x;
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'top':
rect[selected].h += rect[selected].y - mousePos.y;
rect[selected].y = mousePos.y;
break;
case 'left':
rect[selected].w += rect[selected].x - mousePos.x;
rect[selected].x = mousePos.x;
break;
case 'bottom':
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'right':
rect[selected].w = mousePos.x - rect[selected].x;
break;
}
}
if (drag || currentHandle != previousHandle) draw();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
$.each(rect,function(i,item)
{
ctx.fillRect(item.x, item.y, item.w, item.h);
});
if (currentHandle) {
var posHandle = [];
posHandle = point(0, 0);
switch (currentHandle) {
case 'topleft':
posHandle.x = rect[selected].x;
posHandle.y = rect[selected].y;
break;
case 'topright':
posHandle.x = rect[selected].x + rect[selected].w;
posHandle.y = rect[selected].y;
break;
case 'bottomleft':
posHandle.x = rect[selected].x;
posHandle.y = rect[selected].y + rect[selected].h;
break;
case 'bottomright':
posHandle.x = rect[selected].x + rect[selected].w;
posHandle.y = rect[selected].y + rect[selected].h;
break;
case 'top':
posHandle.x = rect[selected].x + rect[selected].w / 2;
posHandle.y = rect[selected].y;
break;
case 'left':
posHandle.x = rect[selected].x;
posHandle.y = rect[selected].y + rect[selected].h / 2;
break;
case 'bottom':
posHandle.x = rect[selected].x + rect[selected].w / 2;
posHandle.y = rect[selected].y + rect[selected].h;
break;
case 'right':
posHandle.x = rect[selected].x + rect[selected].w;
posHandle.y = rect[selected].y + rect[selected].h / 2;
break;
}
ctx.globalCompositeOperation = 'xor';
ctx.beginPath();
ctx.arc(posHandle.x, posHandle.y, handlesSize, 0, 2 * Math.PI);
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
}
}
init();
You can see a live fiddle here js fiddle
Upvotes: 0
Views: 970
Reputation: 9813
The logic in mouseMove
is a little ambiguous, so I try to reform it to a more clear statements:
If not dragging, keep checking if the mouse collides with any rectangle, and if if collides to any, get the rectHandle.
And if you mousedown when rectHandle exist, set drag
to true
.
So we know if drag
is true, you don't have to check collision again, just use selected
which is the selected rect's index you get from collides
to start compute the new rectangle.
Some changes made to work as you expect, with the altered jsfiddle to demo:
collides
function collides(rects, x, y) {
// Set search index to -1 rather than false if nothing collides.
// This will make the return value stick to Number, so we don't need to
// care check if it is 0 and not false in your origin code.
var isCollision = -1;
for (var i = 0, len = rects.length; i < len; i++) {
var left = rects[i].x, right = rects[i].x+rects[i].w;
var top = rects[i].y, bottom = rects[i].y+rects[i].h;
if (right >= x
&& left <= x
&& bottom >= y
&& top <= y) {
isCollision = i;
}
}
return isCollision;
}
gethandle
function getHandle(e,mouse) {
var returned = false;
// Remove this, do it outside.
// selected = collides(rect, e.offsetX, e.offsetY);
if(selected || selected === 0)
{
// You can use the else if logic here, so when a handle is found,
// it don't keep check if other handles are valid.
// But note that when rect is small, if else way would return topleft
// while current code would return right.
if (dist(mouse, point(rect[selected].x, rect[selected].y)) <= handlesSize) returned = 'topleft';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y)) <= handlesSize) returned = 'topright';
if (dist(mouse, point(rect[selected].x, rect[selected].y + rect[selected].h)) <= handlesSize) returned = 'bottomleft';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y + rect[selected].h)) <= handlesSize) returned = 'bottomright';
if (dist(mouse, point(rect[selected].x + rect[selected].w / 2, rect[selected].y)) <= handlesSize) returned = 'top';
if (dist(mouse, point(rect[selected].x, rect[selected].y + rect[selected].h / 2)) <= handlesSize) returned = 'left';
if (dist(mouse, point(rect[selected].x + rect[selected].w / 2, rect[selected].y + rect.h)) <= handlesSize) returned = 'bottom';
if (dist(mouse, point(rect[selected].x + rect[selected].w, rect[selected].y + rect[selected].h / 2)) <= handlesSize) returned = 'right';
}
return returned;
}
mousemove
function mouseMove(e) {
var previousHandle = currentHandle;
// If not dragging, check collision
if (!drag) {
selected = collides(rect, e.offsetX, e.offsetY);
// If collides with something, get handle
// You can move this part into collides
if (selected >= 0) {
currentHandle = getHandle(e,point(e.pageX - this.offsetLeft, e.pageY - this.offsetTop));
}
} else {
// If drag is true, selected and currentHandle is guranteed to have value in your logic.
var mousePos = point(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
var select = rect[selected];
switch (currentHandle) {
case 'topleft':
rect[selected].w += select.x - mousePos.x;
rect[selected].h += select.y - mousePos.y;
rect[selected].x = mousePos.x;
rect[selected].y = mousePos.y;
break;
case 'topright':
rect[selected].w = mousePos.x - rect[selected].x;
rect[selected].h += rect[selected].y - mousePos.y;
rect[selected].y = mousePos.y;
break;
case 'bottomleft':
rect[selected].w += rect[selected].x - mousePos.x;
rect[selected].x = mousePos.x;
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'bottomright':
rect[selected].w = mousePos.x - rect[selected].x;
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'top':
rect[selected].h += rect[selected].y - mousePos.y;
rect[selected].y = mousePos.y;
break;
case 'left':
rect[selected].w += rect[selected].x - mousePos.x;
rect[selected].x = mousePos.x;
break;
case 'bottom':
rect[selected].h = mousePos.y - rect[selected].y;
break;
case 'right':
rect[selected].w = mousePos.x - rect[selected].x;
break;
}
}
// If dragging or currentHandle changed, draw it
if (drag || currentHandle != previousHandle) draw();
}
Upvotes: 1