In this jsFiddle I have a Raphael canvas with a rect that can be clicked or dragged.
If you move the mouse to the rect and then click it, you may slightly move it (the text will show you that). I need in my application to handle the click and ignore the drag movement when the user intended to click. For example, I need to enable the drag only if it's greater than, say, 5 px. Is there a way to make Raphael not move the rect if the intention is just to click?
var paper = Raphael("canvas", 600, 600);
var title = this.paper.text(50, 10, 'click on the square');
var rect = this.paper.rect(20, 20, 30, 30);
rect.attr('fill', 'black')
var start = function () {
this.odx = 0;
this.ody = 0;
},
move = function (dx, dy) {
title.attr('text', 'mouse moved')
this.translate(dx - this.odx, dy - this.ody);
this.odx = dx;
this.ody = dy;
},
up = function () {};
rect.drag(move, start, up);
rect.mousedown(function(e){
title.attr('text', 'mouse down')
})
You can store initial coordinates when the movement has started and ignore while the distance between current coordinates and initial coordinates is less than x pixels.
var paper = Raphael("canvas", 600, 600);
var title = this.paper.text(50, 10, 'click on the square');
var rect = this.paper.rect(20, 20, 30, 30);
var PX_TO_IGNORE = 5; // pixels to ignore
rect.attr('fill', 'black')
var start = function () {
this.odx = 0;
this.ody = 0;
this.initialCor = null;
},
move = function (dx, dy) {
// check if not initialized(if it`s first click)
if(this.initialCor == null) {
this.initialCor = {x: dx, y: dy};
return;
}
// ignore PX_TO_IGNORE pixels(basically circle with radius PX_TO_IGNORE)
if(distance(this.initialCor.x, dx, this.initialCor.y, dy) < PX_TO_IGNORE ) {
return;
}
title.attr('text', 'mouse moved')
this.translate(dx - this.odx, dy - this.ody);
this.odx = dx;
this.ody = dy;
},
up = function () {
title.attr('text', 'mouse up')
};
rect.drag(move, start, up);
rect.mousedown(function(e){
title.attr('text', 'mouse down')
})
// helper function to calcuate distance between two coordinates
function distance(x1,x2,y1,y2) {
return Math.sqrt( Math.pow((x1-x2), 2) + Math.pow((y1-y2), 2) );
}
http://jsfiddle.net/htLn819y/4/
Related
I'm using RaphaelJS to draw paths and circles, and I want to drag and drop a path so that both ends of the line/path are overlapping the circles.
How can I detect, when I drag a line/path is dragged, that both ends are overlapping circles (and, let's say, not the middle of the line)?
Circle:
this.set = paper.set();
this.shape = paper.circle(x, y, 10);
paper.setStart();
this.radius = 10;
this.text = paper.text(x, y - 15, this.name);
this.set[0].hover(
function() {
this.g = this.glow({
color: "#fff",
width: 20,
opacity: .5
});
this.node.style.cursor = 'pointer';
},
function() {
this.g.remove();
}
);
Line:
let connector = paper.path(`M ${startX} ${startY} l 0 25 l 25 50 L ${endX} ${endY}`);
let start = function () {
this.odx = 0;
this.ody = 0;
this.animate({"fill-opacity": 0.2}, 500);
},
move = function (dx, dy) {
this.translate(dx - this.odx, dy - this.ody);
this.odx = dx;
this.ody = dy;
},
drop = function () {
this.animate({"fill-opacity": 1}, 500);
};
connector.drag(move, start, drop);
In the onend callback, you can retrieve the element with event.target which should be of type path. You can then retrieve the coordinates of the boundaries with path.getPointAtLength(0) and path.getPointAtLength(path.getTotalLength())
If you want to detect if one of the points is inside a circle, the condition is:
const circleX = ...;
const circleY = ...;
const circleRadius = ...;
const pointX = ...;
const pointY = ...;
// Is the distance between the point and the center of the circle inferior to the circle radius?
const isPointInsideCircle = Math.sqrt((circleX - pointX) ** 2 + (circleY - pointY) ** 2) < circleRadius;
I have this problem, and I don't know how to deal with it.
I struggled almost 2 hours to fix it. I tried various methods, but with no luck. If it's a nobbish question please forgive me and explain me in depth every step you do.
I have created a canvas, and I drew a circle in it. Now, I want to move the circle with the mouse, but while the mouse is hold. Is there any way to do it, or I'm just wasting time?
Here is the code:
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas> <!-- < -- the canvas -->
<script>
var canv = document.getElementById("canv");
var context = canv.getContext("2d");
var width = canv.offsetWidth-1;
var height = canv.offsetHeight-1;
context.beginPath();
context.arc(width/2, height/2, 75, 0, 2*Math.PI);
context.stroke();
</script>
Some methods I have tried. These one are left in remarks /**/. Other ones I deleted them.
1.
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="circle_move()"></canvas>
function circle_move(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
document.getElementById("canv").addEventListener("onmousemove", function(){
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
});
}
2.
document.getElementById("canv").addEventListener("mousedown",
function(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
);
3.
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onmousedown="launch"></canvas>
function loop(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
function launch(event){
loop();
if(event.which !== 1 || event.which !== 2 || event.which !== 3){
launch(event);
}
}
Animation and UI
When you drag anything on the canvas you are effectively animating the canvas. To get the best results you need to stop thinking in terms of how you did stuff with the DOM and start to codelike you are writing a game.
One function to rule them all.
To coordinate all that is going on you need one function that handles all the animation. Sometimes called the mainLoop in gaming circles it makes working with animated content a lot easier.
The main loop
function mainLoop(time){ // this is called 60 times a second if there is no delay
// clear the canvas ready to be rendered on.
ctx.clearRect(0,0,canvas.width,canvas.height);
updateDisplay(); // call the function that is rendering the display
// get the next frame
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
From the main loop you can check the current app state and call the the appropriate functionality. In this example there is not much to its just calling updateDisplay()
KISS IO events.
The mouse, keyboard, touch, etc event handlers should not be doing any functional logic. Theses event can fire very rapidly, but the display will ony update once every 60th of a second. The is no point rendering from an event that can fire 500 times in a second when the user can only see just over 10% of the effort the CPU is doing.
The best way to write IO events is just as data loggers. Get what info you can as quick as possible and get out, don't write a different event listener for each type of event, keep the code simple, write just one event handler to handle as much as you can. If you need the keyboard add that to the same listener.
var mouse = (function(){
var bounds;
var m = {x:0,y:0,button:false};
function mouseEvent(event){
bounds = event.currentTarget.getBoundingClientRect();
m.x = event.pageX - bounds.left + scrollX;
m.y = event.pageY - bounds.top + scrollY;
if(event.type === "mousedown"){
m.button = true;
}else if(event.type === "mouseup"){
m.button = false;
}
}
m.start = function(element){
["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent));
}
return m;
}())
mouse.start(canvas);
Now you have access to the mouse whenever you need it via the simple mouse interface.
Circles
There is no point adding a nice interface if there is nothing to move about in it. The following is a object that helps manage circle. It creates, draws, and locates circles.
Included is the findClosest function it get the circle under the mouse. It will return the smallest circle under the mouse. If nothing under the mouse it will return undefined.
var circles = {
items [],
drawCircle(){ // function for the circle
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.stroke();
},
each(callback){ // iterator
var i;
for(i = 0; i < this.items.length; i++){
callBack(this.items[i],i);
}
},
drawCircles(){
this.each(c => {
c.draw();
})
},
addCircle(x,y,radius){
var circle = {x, y, radius, draw : this.drawCircle};
this.items.push(circle);
return circle;
},
getClosest(pos){
var minDist, i, dist, x, y, foundCircle;
minDist = Infinity;
this.each(c =>{
x = pos.x - c.x;
y = pos.y - c.y;
dist = Math.sqrt(x * x + y * y);
if(dist <= c.radius && dist < minDist){
minDist = dist;
foundCircle = c;
}
})
return foundCircle;
}
}
How to Drag.
There is a lot involved in dragging objects. You need a function to find the closest object to the mouse. You need to provide feed back so that the user can see what can be dragged.
You may end up with many different drag type events. Drag to create, drag to move, drag something onto the canvas, or manipulate a custom rendered UI object. If you manage the dragging state from one object you can ensure that you dont accidentally click a UI item when dragging a circle.
When dragging starts you check what the drag will be doing. Then you flag that you are dragging, and indicate what the drag is to do. (see updateDisplay function)
While the drag is active do that action. When the mouse is up then just deactivate the drag
var dragging = {
started : false, // true if dragging
type : null, // string with the type of drag event
currentObj : null, // what we are dragging
startX : 0, // info about where the drag started
startY : 0,
start(type, obj){ // called at the start of a drag.
this.startX = mouse.x;
this.startY = mouse.y;
this.started = true;
this.type = type;
this.currentObj = obj;
}
}
Then the render function. It is called once every frame. I checks if the mouse button is down, what to do if it is, sets the cursor so people know what to do, and draws the circles.
var cursor = "default"; // to hold the cursor
var overCircle = null; // this holds whatever circle happens to be under the mouse when not dragging
function updateDisplay(){
var x,y, c;
cursor = "default"; // set default cursor
// check that the mouse if over the canvas
if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
cursor = "crosshair";
}
// is the mouse button down
if(mouse.button){ // the button is down
if(!dragging.started){ // if not dragging start
if(overCircle){ // start a move drag if over a circle
dragging.start("move",overCircle)
overCircle = null;
}else{ // start a create drag
dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
}
}
c = dragging.currentObj;
// Update the drag state o fthe object being draged and the type of drag
if(dragging.type === "create"){
x = c.x - mouse.x;
y = c.y - mouse.y;
c.radius = Math.sqrt(x * x + y * y);
}else if(dragging.type === "move"){
x = dragging.startX - mouse.x;
y = dragging.startY - mouse.y;
c.x -= x;
c.y -= y;
dragging.startX = mouse.x;
dragging.startY = mouse.y;
}
cursor = "none";
} else { // button must be up
if(dragging.started){ // have we been dragging something.
dragging.started = false; // drop it
}
}
// draw the circles
ctx.strokeStyle = "black";
circles.draw();
// if not dragging
if(!dragging.started){
// find circle under the mouse
c = circles.getClosest(mouse);
if(c !== undefined){ // if there is a circle under the mouse highlight it
cursor = "move";
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(0,255,255,0.1)";
c.draw();
ctx.fill();
overCircle = c;
}else{
overCircle = null;
}
}
// set the cursor.
canvas.style.cursor = cursor;
}
As a working example.
var canvas = document.createElement("canvas");
canvas.style.border = "1px black solid";
canvas.width = 512;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
function mainLoop(time){ // this is called 60 times a second if there is no delay
ctx.clearRect(0,0,canvas.width,canvas.height);
updateDisplay(); // call the function that is rendering the display
// get the next frame
requestAnimationFrame(mainLoop);
}
// request the first frame. It will not start untill all the code below has been run
requestAnimationFrame(mainLoop);
var mouse = (function(){
var bounds;
var m = {x:0,y:0,button:false};
function mouseEvent(event){
bounds = event.currentTarget.getBoundingClientRect();
m.x = event.pageX - bounds.left + scrollX;
m.y = event.pageY - bounds.top + scrollY;
if(event.type === "mousedown"){
m.button = true;
}else if(event.type === "mouseup"){
m.button = false;
}
}
m.start = function(element){
["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent));
}
return m;
}())
mouse.start(canvas);
var circles = {
items : [],
drawCircle(){ // function for the circle
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.stroke();
},
each(callback){ // iterator
var i;
for(i = 0; i < this.items.length; i++){
callback(this.items[i],i);
}
},
draw(){
this.each(c => {
c.draw();
})
},
addCircle(x,y,radius){
var circle = {x, y, radius, draw : this.drawCircle};
this.items.push(circle);
return circle;
},
getClosest(pos){
var minDist, i, dist, x, y, foundCircle;
minDist = Infinity;
this.each(c =>{
x = pos.x - c.x;
y = pos.y - c.y;
dist = Math.sqrt(x * x + y * y);
if(dist <= c.radius){
if(foundCircle === undefined || (foundCircle && c.radius < foundCircle.radius)){
minDist = dist;
foundCircle = c;
}
}
})
return foundCircle;
}
}
var dragging = {
started : false,
type : null,
currentObj : null, // what we are dragging
startX : 0,
startY : 0,
start(type, obj){
this.startX = mouse.x;
this.startY = mouse.y;
this.started = true;
this.type = type;
this.currentObj = obj;
}
}
var cursor = "default";
var overCircle = null;
function updateDisplay(){
var x,y, c;
cursor = "default"
if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
cursor = "crosshair";
}
if(mouse.button){ // the button is down
if(!dragging.started){
if(overCircle){
dragging.start("move",overCircle)
overCircle = null;
}else{
dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
}
}
c = dragging.currentObj;
if(dragging.type === "create"){
x = c.x - mouse.x;
y = c.y - mouse.y;
c.radius = Math.sqrt(x * x + y * y);
}else if(dragging.type === "move"){
x = dragging.startX - mouse.x;
y = dragging.startY - mouse.y;
c.x -= x;
c.y -= y;
dragging.startX = mouse.x;
dragging.startY = mouse.y;
}
cursor = "none";
} else { // button must be up
if(dragging.started){ // have we been dragging something.
dragging.started = false; // drop it
}
}
ctx.strokeStyle = "black";
circles.draw();
if(!dragging.started){
c = circles.getClosest(mouse);
if(c !== undefined){
cursor = "move";
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(0,255,255,0.1)";
c.draw();
ctx.fill();
overCircle = c;
}else{
overCircle = null;
}
}
canvas.style.cursor = cursor;
}
<div style="font-family:12px arial;">Click drag to create circle</div>
Note this is written in ES6 and will not run on IE without some type of compiler.
You can see from the comments and code what is happening. Basically you need a global mouse key state variable that lets the function know if there mouse key is depressed. I used the mousemove event to actually kick off the drawing of the circle. Finally, you need the distance between the cursor and center of circle to be less than the radius, otherwise you can drag the circle from outside the circle (which I presume is not what you wanted).
//keep track of previous x and y coordinates
var x0 = 200;
var y0 = 300;
//keep track of mouse key state
var down = false;
var x;
var y;
//draw the initial circle someplace random
var context = document.getElementById("canv").getContext("2d");
context.clearRect(0, 0, 600, 500);
context.arc(200,300, 75, 0, 2*Math.PI);
context.stroke();
document.addEventListener("mousedown", function()
{
//if mousedown event is logged and we are within the area of the circle then state of down is true
if(getDistance(x, x0, y, y0) < 75) down = true;
});
document.addEventListener("mouseup", function()
{
//if mouseup event is logged then down is now false
down = false;
});
document.getElementById("canv").addEventListener("mousemove",
function(event){
x = event.clientX;
y = event.clientY;
//we need to be in "down" state in order for a redraw to be necessary
if(down)
{
//set the previous coordinates to the new coordinates we are drawing.
x0 = x;
y0 = y;
//draw the darn thing
context.beginPath();
context.clearRect(0, 0, 600, 500);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
}
);
function getDistance(x0, x1, y0, y1)
{
return Math.sqrt(Math.pow(x1-x0,2) + Math.pow(y1-y0, 2));
}
<canvas id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas>
var offsetx,offsety,posx,posy,run;
window.onmousedown=function(e){
run=true;
offsetx=e.clientX;
offsety=e.clientY;
};
window.onmouseup=function(){
run=false;
};
window.onmousemove=function(e){
if(run){
movedx=e.clientX-offsetx;//mouse moved this value
movedy=e.clientY-offsety;
posx+=movedx;
posy+=movedyy;
offsetx=e.clientX;
offsety=e.clientY
//todo draw at posx,posy
}
};
//todo draw circle+set posx,posy to its position
I am trying to implement a drag and drop on a canvas representing 3 disks.
I would like to change with mouse the position of each mass. My main problem is that I am constrained by the length of axe for each of these 3 spheres.
For the moment, I have implemented the following function when mouse is moving inside the canvas (value of indexMass indicates which mass is moved: 1, 2 or 3 and t1, t2, t3 represents respectively the angle of mass 1, 2, 3):
// Happens when the mouse is moving inside the canvas
function myMove(event) {
if (isDrag) {
var x = event.offsetX;
var y = event.offsetY;
if (indexMass == 1)
{ // Update theta1 value
t1 = t1 + 0.1*Math.atan(y/x);
}
else if (indexMass == 2)
{ // Update theta2 value
t2 = t2 + 0.1*Math.atan(y/x);
}
else if (indexMass == 3)
{ // Update theta3 value
t3 = t3 + 0.1*Math.atan(y/x);
}
// Update drawing
DrawPend(canvas);
}
}
As you can see, I did for each angle:
t = t + 0.1*Math.atan(y/x);
with:
var x = event.offsetX;
var y = event.offsetY;
But this effect is not very nice. Once the sphere is selected with mouse (on mouse click), I would like the cursor to be stuck with this sphere or the sphere to follow the "delta" of the mouse coordinates when I am not on sphere any more.
Update 1
#Blindman67: thanks for your help, your code snippet is pretty complex for me, I didn't understand it all. But I am on the right way.
I am starting by the first issue: make rotate the selected disk with mouse staying very closed to it or over it, when dragging.
For the moment, I have modified my function myMove (which is called when I have clicked down and move the mouse for dragging) like:
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
// Compute dx and dy before calling DrawPend
var lastX = parseInt(event.offsetX - mx);
var lastY = parseInt(event.offsetY - my);
var dx = lastX - window['x'+indexMass];
var dy = lastY - window['y'+indexMass];
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
where indexMass is the index of dragged disk and window['x'+indexMass] , window['y'+indexMass] are the current coordinates of the selected disk center.
After, I compute the dx, dy respectively from coordinates mouse clicked when starting drag (mx, my returned by getMousePos function) and mouse coordinates with moving.
Finally, I change the angle of disk by set, for global variable (theta of selected disk), i.e window['t'+indexMass]:
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
I have took your part of code with Math.atan2.
But the result of this function doesn't make a good animation with mouse dragging, I would like to know where this could come from.
Right now, I would like to implement only the dragging without modifying the length of axis, I will see more later for this functionality.
Update 2
I keep going on to find a solution about the dragging of a selected mass with mouse.
For trying a synthesis of what I have done previously, I believe the following method is good but this dragging method is not working very well: the selected disk doesn't follow correctly the mouse and I don't know why.
In myMove function (function called when I start dragging), I decided to:
Compute the dx, dy between the mouse coordinates and the selected disk coordinates, i.e:
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
indexMass represents the index of the selected disk.
Increment the position of selected disk (stored in temporary variables tmpX, tmpY) by dx, dy.
Compute the new angle theta (identified in code by global variable window['t'+indexMass]
Compute the new positions of selected disk with this new value of theta, i.e for example with disk1 (indexMass=1 and theta = t1):
x1= x0 +l1 * sin(t1)
y1= y0 +l1 * sin(t1)
I want to draw readers' attention to the fact that I want dragging with mouse not to modify the lengths of axes with mouse, this is a constraint.
Here's the entire myMove function (called when drag is starting) :
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
console.log('offsetX', event.offsetX);
console.log('offsetY', event.offsetY);
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
console.log('dx', dx);
console.log('dy', dy);
// Temp variables
var tmpX = window['x'+indexMass];
var tmpY = window['y'+indexMass];
// Increment temp positions
tmpX += dx;
tmpY += dy;
// Compute new angle for indexMass
window['t'+indexMass] = Math.atan2(tmpX, tmpY);
console.log('printf', window['t'+indexMass]);
// Compute new positions of disks
dragComputePositions();
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
You can not move the OS mouse position. You can hide the mouse canvas.style.cursor = "none"; and then draw a mouse on the canvas your self but it will lag behind by one frame because when you get the mouse coordinates the OS has already placed the mouse at that position, and if you use requestAnimationFrame (RAF) the next presentation of the canvas will be at the next display refresh interval. If you don't use RAF you may or may not present the canvas on the current display refresh, but you will get occasional flicker and shearing.
To solve the problem (which is subjective) draw a line from the rotation point through the ball to the mouse position this will at least give the user some feedback as to what is happening.
I would also add some handles to the balls so you could change the mass (volume of sphere * density) and the length of axis.. The resize cursors are a problem as the will not match the direction of required movement when the angles have changes. You would need to find one closest to the correct angle or render a cursor to a canvas and use that.
Example code shows what I mean. (does not include sim) Move mouse over balls to move, when over you will also see two circles appear to change distance and radius (mass)
/*-------------------------------------------------------------------------------------
answer code
---------------------------------------------------------------------------------------*/
var balls = [];
var startX,startY;
var mouseOverBallIndex = -1;
var mouseOverDist = false;
var mouseOverMass = false;
const DRAG_CURSOR = "move";
const MASS_CURSOR = "ew-resize";
const DIST_CURSOR = "ns-resize";
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
function addBall(dist,radius){
balls.push({
dist : dist,
radius : Math.max(10,radius),
angle : -Math.PI / 2,
x : 0,
y : 0,
mass : (4/3) * radius * radius * radius * Math.PI,
});
}
function drawBalls(){
var i = 0;
var len = balls.length;
var x,y,dist,b,minDist,index,cursor;
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.fillStyle = "blue"
ctx.beginPath();
x = startX;
y = startY;
ctx.moveTo(x, y)
for(; i < len; i += 1){
b = balls[i];
x += Math.cos(b.angle) * b.dist;
y += Math.sin(b.angle) * b.dist;
ctx.lineTo(x, y);
b.x = x;
b.y = y;
}
ctx.stroke();
minDist = Infinity;
index = -1;
for(i = 0; i < len; i += 1){
b = balls[i];
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
if(!dragging){
x = b.x - mouse.x;
y = b.y - mouse.y;
dist = Math.sqrt(x * x + y * y);
if(dist < b.radius + 5 && dist < minDist){
minDist = dist;
index = i;
}
}
}
if(!dragging){
mouseOverBallIndex = index;
if(index !== -1){
cursor = DRAG_CURSOR;
b = balls[index];
ctx.fillStyle = "Red"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
dx = b.x - Math.cos(b.angle) * b.radius;
dy = b.y - Math.sin(b.angle) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverDist = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = DIST_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverDist = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();MASS_CURSOR
dx = b.x - Math.cos(b.angle + Math.PI/2) * b.radius;
dy = b.y - Math.sin(b.angle + Math.PI/2) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverMass = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = MASS_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverMass = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();
canvas.style.cursor = cursor;
}else{
canvas.style.cursor = "default";
}
}else{
b = balls[mouseOverBallIndex];
ctx.fillStyle = "Yellow"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function display(){ // put code in here
var x,y,b
if(balls.length === 0){
startX = canvas.width/2;
startY = canvas.height/2;
addBall((startY * 0.8) * (1/4), startY * 0.04);
addBall((startY * 0.8) * (1/3), startY * 0.04);
addBall((startY * 0.8) * (1/2), startY * 0.04);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if((mouse.buttonRaw & 1) && mouseOverBallIndex > -1){
b = balls[mouseOverBallIndex];
if(dragging === false){
dragging = true;
dragStartX = balls[mouseOverBallIndex].x;
dragStartY = balls[mouseOverBallIndex].y;
}else{
b = balls[mouseOverBallIndex];
if(mouseOverBallIndex === 0){
x = startX;
y = startY;
}else{
x = balls[mouseOverBallIndex-1].x
y = balls[mouseOverBallIndex-1].y
}
if(mouseOverDist){
var dist = Math.sqrt(Math.pow(x-mouse.x,2)+Math.pow(y-mouse.y,2));
b.dist = dist + b.radius;
}else
if(mouseOverMass){
var dist = Math.sqrt(Math.pow(dragStartX-mouse.x,2)+Math.pow(dragStartY-mouse.y,2));
b.radius = Math.max(10,dist);
b.mass = dist * dist * dist * (4/3) * Math.PI;
}else{
b.angle = Math.atan2(mouse.y - y, mouse.x - x);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "grey";
ctx.moveTo(x,y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
}
}
}else if(dragging){
dragging = false;
}
drawBalls();
}
/*-------------------------------------------------------------------------------------
answer code END
---------------------------------------------------------------------------------------*/
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; balls.length = 0; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
// continue until mouse right down
if (!(mouse.buttonRaw & 2)) { requestAnimationFrame(update); } else { done(); }
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
(Posted a solution from the question author to move it to the answer space).
Problem solved! I forgot to take into account the position of "indexMass-1" disk to compute the new angle with Math.atan2 function.
I am trying to create Simon game using HTML5 canvas and vanilla JavaScript. I am confused about the coordinate system in the arc() method. I have divided the circle into 4 quadrants and would like to alert the number of the quadrant clicked. But, I am not sure how to find out which part of the circle was clicked. https://jsfiddle.net/xawpLdys/1/
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var pads = [];
var angle = 2 * Math.PI / 4;
var color = ["green","red","blue","yellow"];
var Pads = function(x, y, radius, start, end) {
this.x = x;
this.y = y;
this.radius = radius;
this.start = start;
this.end = end;
};
function drawSimon(radius) {
for (var i = 0; i < 4; i++) {
context.beginPath();
context.moveTo(x, y);
context.arc(x, y, radius, i*angle, (i+1)*angle, false);
context.lineWidth = radius;
context.fillStyle = color[i];
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#444';
context.stroke();
var pad = new Pads(x, y, radius, i*angle, (i+1)*angle);
pads.push(pad);
}
}
drawSimon(150);
$('#myCanvas').click(function (e) {
/*for (var i = 0; i < pads.length; i++) {
if (//condition matches) {
alert (i);
}
}*/
});
Try This
This example just translates the clicked e.pageX and e.pageY to normal quadrant system. And after some condition, you can determine which part has been clicked.
$('#myCanvas').click(function (e) {
var nx,ny;
nx=-(x- e.pageX);
ny=y- e.pageY;
if (nx>0 && ny>0){
alert('Yellow');
}else if (nx<0 && ny>0){
alert('Blue');
}else if (nx>0 && ny<0){
alert('Green');
}else if (nx<0 && ny<0){
alert('Red');
}
});
Here is the fiddle https://jsfiddle.net/xawpLdys/3/
UPDATE
John S was right, (It counts clicks that are outside the circle). To prevent the clicks outside the circle from considering, we need to just find the distance from the center of the circle and the clicked point. Then compare the distance with the circle's radius to see it is inside radius.
The updated code :
$('#myCanvas').click(function (e) {
var nx,ny;
nx=-(x- e.pageX);
ny=y- e.pageY;
var dx = Math.abs(Math.abs(x)-Math.abs(e.pageX));
var dy = Math.abs(Math.abs(y)-Math.abs(e.pageY));
var distance_clicked = Math.sqrt((dx*dx)+(dy*dy));
if(distance_clicked <= radius){
if (nx>0 && ny>0){
alert('Yellow');
}else if (nx<0 && ny>0){
alert('Blue');
}else if (nx>0 && ny<0){
alert('Green');
}else if (nx<0 && ny<0){
alert('Red');
}
}
});
Here is the updated fiddle https://jsfiddle.net/xawpLdys/8/
It still have the limitations of dividing the circle more than 4 slices.
The accepted answer seems a bit limited. It counts clicks that are outside the circle. That could be fixed fairly easily, but it would still be limited to four sections.
To determine if a point is in a sector:
First check if it is within the circle. The Pythagorean theorem comes into play here. Square the x and y values, if their sum is less than or equal to the radius squared, the point is in the circle.
If the point is in the circle, then check if its angle is between the start and end angles for the sector. You can get the point's angle using the arc tangent function from trigonometry.
Try this jsfiddle.
Here are the types that help make this work:
var Circle = function(center, radius) {
this.center = center;
this.radius = radius;
this._radiusSquared = Math.pow(this.radius, 2);
}
$.extend(Circle.prototype, {
containsPoint: function(point) {
var relPoint = this.pointToRelPoint(point);
return Math.pow(relPoint.x, 2) + Math.pow(relPoint.y, 2)
<= this._radiusSquared;
},
getAngleForPoint: function(point) {
var relPoint = this.pointToRelPoint(point);
return Math.atan2(-relPoint.y, -relPoint.x) + Math.PI;
},
pointToRelPoint: function(point) {
return { x: point.x - this.center.x, y: point.y - this.center.y }
}
});
var CircleSector = function(startAngle, endAngle) {
this.startAngle = startAngle;
this.endAngle = endAngle;
};
$.extend(CircleSector.prototype, {
containsAngle: function(angle) {
return (angle >= this.startAngle) && (angle < this.endAngle);
},
containsPoint: function(circle, point) {
return circle.containsPoint(point)
&& this.containsAngle(circle.getAngleForPoint(point));
}
});
Let's say I have a canvas that is split into a 15x10 32-pixel checkboard. Thus, I have this:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var tileSize = 32;
var xCoord
var yCoord
var tilesX = 15; // tiles across
var tilesY = 10; // tiles up and down
var counted = 1; // for drawing purpose for checkerboard for visual guidance
var mouseSel = new Image()
mouseSel.src = 'http://i.imgur.com/vAA03NB.png' // mouse selection
mouseSel.width = 32
mouseSel.height = 32
function isOdd(num) {
return num % 2;
}
function getMousePos(canvas, evt) {
// super simple stuff here
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
drawCanvas(); // upon intilization... draw
function drawCanvas() {
for (var y = 0; y <= 10; y++) {
for (var x = 0; x <= 15; x++) {
if (isOdd(counted)) {
context.fillStyle = '#dedede'
context.fillRect(x * 32, y * 32, 32, 32);
// checkboard drawn complete.
}
counted++;
} // end first foor loop
counted++;
} // end last for loop
if (counted >= 176) counted = 1 // once all tiles (16x11) are drawn... reset counter for next instance
}
canvas.addEventListener('mousemove', function (evt) {
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas so mouse isn't stuck
drawCanvas(); // draw checkboard
// get the actual x,y position of 15x10 32-pixel checkboard
var mousePos = getMousePos(canvas, evt);
mousePos.xCoord = Math.floor(mousePos.x / tileSize)
mousePos.yCoord = Math.floor(mousePos.y / tileSize)
// draw the mouse selection
context.drawImage(mouseSel, (mousePos.xCoord * 32), (mousePos.yCoord * 32), 32, 32) // draw mouse selection
// debug
var message = ' (' + mousePos.xCoord + ',' + mousePos.yCoord + ') | (' + mousePos.x + ',' + mousePos.y + ')';
var textarea = document.getElementById('debug');
textarea.scrollTop = textarea.scrollHeight;
$('#debug').append(message + '\n');
}, false);
canvas#canvas {
background: #ABABAB;
position: relative;
z-index: 1;
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" height="352" width="512" tabindex="0"></canvas>
<textarea name="" id="debug" cols="30" rows="35"></textarea>
**NOTE: ** Make sure to scroll down in that preview pane so you can see the debug textarea.
As you can see, the event of "drawing" fires EVERY single time it moves. That means every pixel.
I am trying to figure out how to make the drawing fire ONLY when a new x,y coord has changed. Because it'd be useless to redraw the mouse selection when it's only moved 5 pixels across and it's still going to be drawn at the same place.
My suggestion
Upon entering, have a temporary value and when that is passed, to redraw again?
Make a temporary value and update that if it was different from before. Then put the code in an if statement where either have changed.
var tempX, tempY;
var newX = 100;
var newY = 100;
tempX = mousePos.xCoord;
tempY = mousePos.yCoord;
if (newX !== tempX || newY !== tempY) {
// code here
}
if (tempX !== newX) newX = mousePos.xCoord;
if (tempY !== newY) newY = mousePos.yCoord;
JSFiddle: http://jsfiddle.net/weka/bvnma354/8/