I want to rotate a shape on canvas on mouse move, and I want to detect if mouse is moving in clockwise direction
or not, but I do not know how to do that. Here is my code:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var annotation_rect = canvas.getBoundingClientRect();
rect = {
startX : 150,
startY : 50,
w : 250,
h : 150,
endX : 0,
endY : 0,
rotate: 0
};
var drag = false;
var rotating = false;
var update = true; // when true updates canvas
var rotate_angle = 5; // in degrees - for rotating blurred part
var angle = rotate_angle * (Math.PI / 180);
var original_source = img.src;
img.src = original_source;
function rotateRight(){
rect.rotate += angle;
update = true;
}
function rotateLeft(){
rect.rotate -= angle;
update = true;
}
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.shadowBlur = 5;
ctx.filter = 'blur(10px)';
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
ctx.lineWidth = 3;
ctx.strokeStyle = "#fff";
ctx.fillStyle = "#fff";
ctx.fill();
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
drag = true;
}
function mouseUp() { drag = false; update = true; }
function startRotation(e){
rotating = true;
}
function stopRotation(e){
rotating = false;
}
function onShapeRotating(e){
if(rotating){
rotateRight();
}
}
function mouseMove(e) {
var mouseX = e.offsetX - annotation_rect.left,
mouseY = e.offsetY - annotation_rect.top,
endX = rect.startX + rect.w,
endY = rect.startY + rect.h
var cursorOnShape = mouseX >= rect.startX && mouseX <= endX && mouseY >= rect.startY && mouseY <= endY;
if(cursorOnShape){
canvas.style.cursor = "pointer"
canvas.addEventListener('mousedown', startRotation, false);
canvas.addEventListener('mouseup', stopRotation, false);
canvas.addEventListener('mousemove', onShapeRotating, false);
}else{
canvas.style.cursor = "default"
canvas.removeEventListener('mousedown', startRotation, false);
canvas.removeEventListener('mouseup', stopRotation, false);
canvas.removeEventListener('mousemove', onShapeRotating, false);
}
}
init();
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="https://carsales.pxcrush.net/carsales/car/cil/cc5166737225893351785.jpg?width=600&height=300&overlay&aspect=FitWithIn&watermark=1439104668"/>
<canvas id="canvas"></canvas>
</div>
Thus I have an rect drawn on canvas and I detect if the mouse is on that rect and if it is I call the function rotateShape
and there I call the function rotateRight
. And that is working. If you are on the rect with mouse and you press it down and rotate it would rotate the rect calling the function rotateRight
.
But I want to be able to check if the mouse is moving in clockwise direction or not. If it moves in clockwise direction I would call rotateRight
and if not then I would call rotateLeft
.
Any idea how to do that?
Here is the fiddle.
The problem with using Math.atan2()
is that the difference between Math.atan2(1,-10)
and Math.atan2(-1,-10)
is 6.084 not 0.199 which means as you accumulate the sum of the differences you never get a value over Math.PI
or under -Math.PI
In other words you can not directly track the number of turns you have made nor is it simple to know if you have turned clockwise or counter clockwise.
The best way to compute the direction and amount the mouse has rotated around a point is to use the cross product of the two vectors from the center to the mouse position and old position
// cx,cy center of rotation
// ox,oy old position of mouse
// mx,my new position of mouse.
function getAngle(cx, cy, ox, oy, mx, my){
var x1 = ox - cx;
var y1 = oy - cy;
var x2 = mx - cx;
var y2 = my - cy;
var d1 = Math.sqrt(x1 * x1 + y1 * y1);
var d2 = Math.sqrt(x2 * x2 + y2 * y2);
return Math.asin((x1 / d1) * (y2 / d2) - (y1 / d1) * (x2 / d2));
}
The function will return a negative change in angle if counter clockwise and positive if clockwise.
Example show the above function being used to track the total rotation, not the absolute orientation as the other answers do.
const mouse = {x : 0, y : 0, ox : 0, oy : 0, down : false};
["down","up","move"].forEach(name => document.addEventListener("mouse" + name,mouseEvents));
const ctx = canvas.getContext("2d");
function mouseEvents(e) {
mouse.x = e.pageX; mouse.y = e.pageY;
mouse.down = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.down;
}
function getAngleBetween(cx, cy, ox, oy, mx, my) {
var x1 = ox - cx;
var y1 = oy - cy;
var x2 = mx - cx;
var y2 = my - cy;
// max to prevent div by zero
var d1 = Math.max(0.001, Math.sqrt(x1 * x1 + y1 * y1));
var d2 = Math.max(0.001, Math.sqrt(x2 * x2 + y2 * y2));
return Math.asin((x1 / d1) * (y2 / d2) - (y1 / d1) * (x2 / d2));
}
var w, h, cw, ch;
var angle = 0;
function update() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
var change = 0;
if (mouse.down) {
change = getAngleBetween(cw, ch, mouse.ox, mouse.oy, mouse.x, mouse.y);
angle += change;
}
ctx.setTransform(1, 0, 0, 1, cw, ch);
ctx.rotate(angle);
ctx.fillRect(-100, -25, 200, 50);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.font = "28px arial";
ctx.textAlign = "center";
ctx.fillText("Total rotation : " + ((angle * 180 / Math.PI) | 0), cw, 30);
ctx.font = "14px arial";
ctx.fillText("Change : " + (change * 180 / Math.PI).toFixed(2), cw, 48);
ctx.fillText("Click drag to rotate box.", cw, h - 20);
mouse.ox = mouse.x;
mouse.oy = mouse.y
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Instead of rotating by a fixed value, rotate by the difference between the last known angle and the new one.
To get these angles, you can use
var angle = Math.atan2(
e.clientY - (rect.startY + rect.h /2),
e.clientX - (rect.startX + rect.w /2)
);
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var annotation_rect = canvas.getBoundingClientRect();
rect = {
startX: 150,
startY: 50,
w: 250,
h: 150,
endX: 0,
endY: 0,
rotate: 0
};
var drag = false;
var rotating = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
// keep track of the last angle
var prevAngle = null;
// a single function
function rotate(angle) {
rect.rotate += angle;
update = true;
}
// called on mousemove when dragging
function onShapeRotating(e) {
if (rotating) {
var angle = Math.atan2(
e.clientY - (rect.startY + rect.h / 2),
e.clientX - (rect.startX + rect.w / 2)
);
if (prevAngle !== null)
rotate(angle - prevAngle)
prevAngle = angle;
}
}
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect() {
ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.shadowBlur = 5;
ctx.filter = 'blur(10px)';
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
ctx.lineWidth = 3;
ctx.strokeStyle = "#fff";
ctx.fillStyle = "#fff";
ctx.fill();
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
drag = true;
}
function mouseUp() {
prevAngle = null;
drag = false;
update = true;
}
function startRotation(e) {
rotating = true;
}
function stopRotation(e) {
rotating = false;
}
function mouseMove(e) {
var mouseX = e.offsetX - annotation_rect.left,
mouseY = e.offsetY - annotation_rect.top,
endX = rect.startX + rect.w,
endY = rect.startY + rect.h
var cursorOnShape = mouseX >= rect.startX && mouseX <= endX && mouseY >= rect.startY && mouseY <= endY;
if (cursorOnShape) {
canvas.style.cursor = "pointer"
canvas.addEventListener('mousedown', startRotation, false);
canvas.addEventListener('mouseup', stopRotation, false);
canvas.addEventListener('mousemove', onShapeRotating, false);
} else {
canvas.style.cursor = "default"
canvas.removeEventListener('mousedown', startRotation, false);
canvas.removeEventListener('mouseup', stopRotation, false);
canvas.removeEventListener('mousemove', onShapeRotating, false);
}
}
init();
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="https://carsales.pxcrush.net/carsales/car/cil/cc5166737225893351785.jpg?width=600&height=300&overlay&aspect=FitWithIn&watermark=1439104668" />
<canvas id="canvas"></canvas>
</div>
Now, your code has a lot of things to be fixed, but I'll let it to you.
You'd be better have single events on the whole canvas/document rather than adding ones selectively.
Your cursorOnShape
algo doesn't take into account the rotation of your rectangle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With