Detect if Mouse is over an object inside canvas - javascript

I have created a line inside a canvas element. I am looking for the easiest way to detect if the position of the mouse is inside the line, which is inside the canvas.
I have used this function to see the position of the mouse inside the canvas, but I am very confused on how I should proceed.
function getMousePos(c, evt) {
var rect = c.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
I have also looked at this topic Fabricjs detect mouse over object path , but it detects if the mouse is inside the canvas, not inside the object.
The line that I create is a part of smaller lines, connected to each other.
for (var i = 0; i < 140 ; i++) {
ctx.beginPath();
ctx.moveTo(x[i],y[i]);
ctx.quadraticCurveTo(x[i],50,x[i+1],y[i+1]);
ctx.lineWidth = 40;
ctx.strokeStyle = 'white';
ctx.lineCap = 'round';
ctx.stroke();
}
where x[i] and y[i] are the arrays with the coordinates that I want.
I hope my question is clear, although I am not very familiar with javascript.
Thanks
Dimitra

A Demo: http://jsfiddle.net/m1erickson/Cw4ZN/
You need these concepts to check if the mouse is inside a line:
Define the starting & ending points of a line
Listen for mouse events
On mousemove, check if the mouse is within a specified distance of the line
Here's annotated example code for you to learn from.
$(function() {
// canvas related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
// dom element to indicate if mouse is inside/outside line
var $hit = $("#hit");
// determine how close the mouse must be to the line
// for the mouse to be inside the line
var tolerance = 5;
// define the starting & ending points of the line
var line = {
x0: 50,
y0: 50,
x1: 100,
y1: 100
};
// set the fillstyle of the canvas
ctx.fillStyle = "red";
// draw the line for the first time
draw(line);
// function to draw the line
// and optionally draw a dot when the mouse is inside
function draw(line, mouseX, mouseY, lineX, lineY) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(line.x0, line.y0);
ctx.lineTo(line.x1, line.y1);
ctx.stroke();
if (mouseX && lineX) {
ctx.beginPath();
ctx.arc(lineX, lineY, tolerance, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
}
// calculate the point on the line that's
// nearest to the mouse position
function linepointNearestMouse(line, x, y) {
//
lerp = function(a, b, x) {
return (a + x * (b - a));
};
var dx = line.x1 - line.x0;
var dy = line.y1 - line.y0;
var t = ((x - line.x0) * dx + (y - line.y0) * dy) / (dx * dx + dy * dy);
var lineX = lerp(line.x0, line.x1, t);
var lineY = lerp(line.y0, line.y1, t);
return ({
x: lineX,
y: lineY
});
};
// handle mousemove events
// calculate how close the mouse is to the line
// if that distance is less than tolerance then
// display a dot on the line
function handleMousemove(e) {
e.preventDefault();
e.stopPropagation();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
if (mouseX < line.x0 || mouseX > line.x1) {
$hit.text("Outside");
draw(line);
return;
}
var linepoint = linepointNearestMouse(line, mouseX, mouseY);
var dx = mouseX - linepoint.x;
var dy = mouseY - linepoint.y;
var distance = Math.abs(Math.sqrt(dx * dx + dy * dy));
if (distance < tolerance) {
$hit.text("Inside the line");
draw(line, mouseX, mouseY, linepoint.x, linepoint.y);
} else {
$hit.text("Outside");
draw(line);
}
}
// tell the browser to call handleMousedown
// whenever the mouse moves
$("#canvas").mousemove(function(e) {
handleMousemove(e);
});
}); // end $(function(){});
body {
background-color: ivory;
}
canvas {
border: 1px solid red;
}
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<h2 id="hit">Move mouse near line</h2>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
About hit-testing Paths:
If you create Paths using path commands you can use context.isPointInPath(mouseX,mouseY) to check if the mouse is inside a path. context.isPointInPath does not work well with lines however because lines theoretically have zero width to "hit".

this.element.addEventListener("mouseenter", (event) => {});
mouse over a div
this.element.addEventListener("mouseleave", (event) => {});
mouse leaving div

Related

Draw line from one rectangle to another rectangle ie a setup a connection on JavaScript Canvas

I was trying to build a project in which a user can add a rectangle and set up a connection between them by drawing a line, that is double click on one rectangle to start and double-clicking on the other rectangle to finish the line. The rectangle can be moved anywhere in the canvas and lines should be intact.This code helps me to add the feature rectangle move I have added 2 functionality 1) Add rectangle on clicking a button 2) Save the position on clicking save. Now I want to add the lines on the canvas to connect the rectangles
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: ivory;
}
#canvas {
border: 1px solid red;
}
#canvasimg {
position: absolute;
top: 10%;
left: 52%;
display: none;
}
</style>
</head>
<body>
<h4>Drag one or more of the shapes</h4>
<canvas id="canvas" width=300 height=300></canvas>
<button onclick="addShape()">Add Rectangle</button>
<button onclick="save()">Save</button>
<script src="canvas.js"></script>
</body>
</html>
Script.js
function save() {
var shape = JSON.stringify(shapes, ['x', 'y', 'number'])
console.log(shape);
var blob = new Blob(
[shape],
{ type: "contentType" });
var url = window.URL.createObjectURL(blob);
var anchor = document.createElement("a");
anchor.href = url;
anchor.download = "test.txt";
anchor.click();
window.URL.revokeObjectURL(url);
document.removeChild(anchor);
}
// get canvas related references
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var BB = canvas.getBoundingClientRect();
var offsetX = BB.left;
var offsetY = BB.top;
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
// drag related variables
var dragok = false;
var startX;
var startY;
// an array of objects that define different shapes
var shapes = [];
var number = 0;
// add rectangle on click
function addShape() {
shapes.push({ x: 0, y: 0, width: 30, height: 30, fill: "#444444", isDragging: false, number: number });
draw();
number++;
}
// listen for mouse events
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.onmousemove = myMove;
// call to draw the scene
draw();
// draw a single rect
function rect(r) {
ctx.fillStyle = r.fill;
ctx.fillRect(r.x, r.y, r.width, r.height);
}
// draw a single rect
function circle(c) {
ctx.fillStyle = c.fill;
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
// clear the canvas
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
// redraw the scene
function draw() {
clear();
// redraw each shape in the shapes[] array
for (var i = 0; i < shapes.length; i++) {
// decide if the shape is a rect or circle
// (it's a rect if it has a width property)
if (shapes[i].width) {
rect(shapes[i]);
} else {
circle(shapes[i]);
};
}
}
// handle mousedown events
function myDown(e) {
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
var mx = parseInt(e.clientX - offsetX);
var my = parseInt(e.clientY - offsetY);
// test each shape to see if mouse is inside
dragok = false;
for (var i = 0; i < shapes.length; i++) {
var s = shapes[i];
// decide if the shape is a rect or circle
if (s.width) {
// test if the mouse is inside this rect
if (mx > s.x && mx < s.x + s.width && my > s.y && my < s.y + s.height) {
// if yes, set that rects isDragging=true
dragok = true;
s.isDragging = true;
}
} else {
var dx = s.x - mx;
var dy = s.y - my;
// test if the mouse is inside this circle
if (dx * dx + dy * dy < s.r * s.r) {
dragok = true;
s.isDragging = true;
}
}
}
// save the current mouse position
startX = mx;
startY = my;
}
// handle mouseup events
function myUp(e) {
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
// clear all the dragging flags
dragok = false;
for (var i = 0; i < shapes.length; i++) {
shapes[i].isDragging = false;
}
}
// handle mouse moves
function myMove(e) {
// if we're dragging anything...
if (dragok) {
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
var mx = parseInt(e.clientX - offsetX);
var my = parseInt(e.clientY - offsetY);
// calculate the distance the mouse has moved
// since the last mousemove
var dx = mx - startX;
var dy = my - startY;
// move each rect that isDragging
// by the distance the mouse has moved
// since the last mousemove
for (var i = 0; i < shapes.length; i++) {
var s = shapes[i];
if (s.isDragging) {
s.x += dx;
s.y += dy;
}
}
// redraw the scene with the new rect positions
draw();
// reset the starting mouse position for the next mousemove
startX = mx;
startY = my;
}
}

Circle Following Mouse HTML5 Canvas jQuery

I am trying to make a circle follow the mouse in HTML Canvas which I am using in a game. I am trying to make the circle move 5px per iteration, but it goes slower when traveling horizontal and faster when it goes vertical. Here's the math that I used:
x=distance between mouse and circle on the x-axis
y=distance between mouse and circle on the y-axis
z=shortest distance between mouse and circle
a=number of units circle should move along the x-axis
b=number of units circle should move along the y axis
x^2 + y^2=z^2
Want the total distance traveled every iteration to be five pixels
a^2 + b^2 = 25
b/a=y/x
b=ay/x
a=sqrt(25-ay/x^2)
a^2+ay/x-25=0
Use Quadratic formula to find both answers
a=(-y/x+-sqrt(y/x)^2+100)/2
I replicated the problem in the code below
$(function(){
let canvas = $("canvas")[0];
let ctx = canvas.getContext("2d");
//Gets position of mouse and stores the value in variables mouseX and mouseY
let mouseX = mouseY = 0;
$("canvas").mousemove(function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}).trigger("mousemove");
let circleX = 0;
let circleY = 0;
function loop(t){
//Background
ctx.fillStyle="blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
let xFromMouse = mouseX-circleX;
let yFromMouse = mouseY-circleY;
let yxRatio = yFromMouse/xFromMouse;
let xyRatio = xFromMouse/yFromMouse;
let speed = 25;
let possibleXValues = [(-yxRatio+Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2,(-yxRatio-Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2];
//I use this code as a temporary fix to stop the circle from completely disappearing
if(xFromMouse === 0 || isNaN(yxRatio) || isNaN(possibleXValues[0]) || isNaN(possibleXValues[1])){
possibleXValues = [0,0];
yxRatio = 0;
}
//Uses b=ay/x to calculate for y values
let possibleYValues = [possibleXValues[0]*yxRatio,possibleXValues[1]*yxRatio];
if(xFromMouse >= 0){
circleX += possibleXValues[0];
circleY += possibleYValues[0];
} else {
circleX += possibleXValues[1];
circleY += possibleYValues[1];
}
ctx.beginPath();
ctx.arc(circleX, circleY, 25, 0, 2 * Math.PI,false);
ctx.fillStyle = "red";
ctx.lineWidth = 0;
ctx.fill();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="450" height="250"></canvas>
I think you may be better using a cartesian to polar conversion. Here's an example from something I made previously. This will allow you to have a consistent step per iteration "speed".
//Canvas, context, mouse.
let c, a, m = { x:0, y:0};
//onload.
window.onload = function(){
let circle = {},
w, h,
speed = 5; //step speed = 5 "pixels" (this will be fractional in any one direction depending on direction of travel).
//setup
c = document.getElementById('canvas');
a = c.getContext('2d');
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
function move(){
//get distance and angle from mouse to circle.
let v1m = circle.x - m.x,
v2m = circle.y - m.y,
vDm = Math.sqrt(v1m*v1m + v2m*v2m),
vAm = Math.atan2(v2m, v1m);
//if distance is above some threshold, to stop jittering, move the circle by 'speed' towards mouse.
if(vDm > speed) {
circle.x -= Math.cos(vAm) * speed;
circle.y -= Math.sin(vAm) * speed;
}
}
function draw(){
//draw it all.
a.fillStyle = "blue";
a.fillRect(0,0,w,h);
a.fillStyle = "red";
a.beginPath();
a.arc(circle.x, circle.y, circle.r, Math.PI * 2, false);
a.closePath();
a.fill();
}
circle = {x:w/2, y:h/2, r:25};
function animate(){
requestAnimationFrame(animate);
move();
draw();
}
c.onmousemove = function(e){
m.x = e.pageX;
m.y = e.pageY;
};
animate();
}
<canvas id="canvas" width="450" height="250"></canvas>

Drawing a circle with random color in canvas on mouse click

I am trying to develop a simple canvas application using html5 and javascript. I want to make a circle depending on the position of mouse click in the canvas. Each time user clicks in canvas a circle should be drawn. Moreover the color of circle needs to be randomly selected. I have written the random color and position function to get x and y position of mouse in canvas. But when i run nothing is happening.
Here is my html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="circle.js"></script>
<style type="text/css">
#testCanvas {
border: 2px solid;
}
</style>
</head>
<body>
<canvas id="testCanvas" width="400" height="400"> </canvas>
</body>
Here's my javascript code:
window.onload = init;
function init() {
// access the canvas element and its context
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var pos = getMousePos(canvas, e);
posx = pos.x;
posy = pos.y;
context.fillStyle = randomColor();
// fill a circle
context.beginPath();
context.arc(posx,posy, 30, 0, 2 * Math.PI, true);
context.fill();
context.closePath();
}
function randomColor() {
var color = [];
for (var i = 0; i < 3; i++) {
color.push(Math.floor(Math.random() * 256));
}
return 'rgb(' + color.join(',') + ')';
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
You need to implement a click handler for the canvas so that every time you click on it you will receive a notification in form of an event:
// access the canvas element and its context
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
// add click handler
canvas.onclick = function(e) {
var pos = getMousePos(canvas, e); // get position as before
context.fillStyle = randomColor(); // get the fill color
// fill a circle
context.beginPath(); // now we can draw the circle at click
context.arc(pos.x, pos.y, 30, 0, 2 * Math.PI); // use pos object directly like this
context.fill();
// closePath() not needed here and won't work after fill() has been called anyways
}
function randomColor() {
var color = [];
for (var i = 0; i < 3; i++) {
color.push(Math.floor(Math.random() * 256));
}
return 'rgb(' + color.join(',') + ')';
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas {border: 1px solid #999}
<canvas id="testCanvas" width="400" height="400"> </canvas>
Here is another example / way of adding an event handler. You can change 'click' to 'mousemove' and others to try different things.
// access the canvas element and its context
window.onload = init;
function init(){
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
function drawCircle(pos,canvas){
posx = pos.x;
posy = pos.y;
context.fillStyle = randomColor();
// fill a circle
context.beginPath();
context.arc(posx,posy, 30, 0, 2 * Math.PI, true);
context.fill();
context.closePath();
}
function randomColor() {
var color = [];
for (var i = 0; i < 3; i++) {
color.push(Math.floor(Math.random() * 256));
}
return 'rgb(' + color.join(',') + ')';
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
} ;
}
canvas.addEventListener('click', function(evt){
var mousePos = getMousePos(canvas,evt);
drawCircle(mousePos,canvas);
},false);
}

hiding or erasing the the circle from canvas if user clicks on same area

Hi i am trying to implement the something into my current app. I want to hide the overlapping circles if user clicks at a point where new circle comes over the old circle. So i have to erase or hide the circle drawn previously and then make new circle in canvas. My current code is running well but without hidding the overlapping circles.So i am stuck on it. I dont know how to make a json array for storing the positions of circles drawn and then removing them if user clicks or draws near the same circle. Circle radius i have kept as 30. Here's my current code.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Canvas Sample -- Arc Shapes</title>
<meta charset="utf-8">
<script src="circle2.js"></script>
<style type="text/css">
#testCanvas {
border: 1px solid #999
}
</style>
</head>
<body>
<canvas id="testCanvas" width="400" height="400"> </canvas>
</body>
</html>
//javascript code
window.onload = init;
// access the canvas element and its context
function init() {
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
// add click handler
canvas.onclick = function(e) {
var pos = getMousePos(canvas, e); // get position as before
context.fillStyle = randomColor(); // get the fill color
var path=[]; //array to store the positions.
// fill a circle
context.beginPath();
context.arc(pos.x, pos.y, 30, 0, 2 * Math.PI);
context.fill();
}
}
function randomColor() {
var color = [];
for (var i = 0; i < 3; i++) {
color.push(Math.floor(Math.random() * 256));
}
return 'rgb(' + color.join(',') + ')';
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
You simply need to store the coordinates of each circle when the mouse is clicked. You can use an array for that. Then you'll need a bunch of methods to process mouse click, circle intersection and finally, drawing them.
I've modified the your script as follows:
// access the canvas element and its context
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var circles = []; // An empty array to hold our circles
// add click handler
canvas.onclick = function(e) {
var pos = getMousePos(canvas, e);
addCircle(pos.x, pos.y);
}
function addCircle(mouse_x, mouse_y) {
// First, we check if there is any intersection with existing circles
for (var i = circles.length - 1; i > 0; i--) {
var circle = circles[i],
distance = getDistance(circle.x, circle.y, mouse_x, mouse_y);
// If distance is less than radius times two, then we know its a collision
if (distance < 60) {
circles.splice(i, 1); // Remove the element from array
}
}
// Second, we push the new circle in the array
circles.push({
x: mouse_x,
y: mouse_y,
color: randomColor()
});
// Third, we draw based on what circles we have in the array
drawCircles();
}
function drawCircles() {
// We'll have to clear the canvas as it has deleted circles as well
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i = circles.length - 1; i > 0; i--) {
var circle = circles[i];
context.fillStyle = circle.color;
context.beginPath();
context.arc(circle.x, circle.y, 30, 0, 2 * Math.PI);
context.fill();
}
}
// Function to get distance between two points
function getDistance(x1, y1, x2, y2) {
// Distance formula
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
function randomColor() {
var color = [];
for (var i = 0; i < 3; i++) {
color.push(Math.floor(Math.random() * 256));
}
return 'rgb(' + color.join(',') + ')';
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas {
border: 1px solid #999
}
<canvas id="testCanvas" width="400" height="400"> </canvas>

HTML5 Canvas - Dragging Text on Canvas Problem

I want to Drag Text which is located on the Canvas , I found a tutorial How to Drag a Rectange but i could not implement it on text , This is the Following code for moving the rectangle , can some one help me to implement it on Text ?
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Canvas Drag and Drop Test</title>
</head>
<body>
<section>
<div>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<script type="text/javascript">
var canvas;
var ctx;
var x = 75;
var y = 50;
var dx = 5;
var dy = 3;
var WIDTH = 400;
var HEIGHT = 300;
var dragok = false;
function rect(x,y,w,h) {
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
return setInterval(draw, 10);
}
function draw() {
clear();
ctx.fillStyle = "#FAF7F8";
rect(0,0,WIDTH,HEIGHT);
ctx.fillStyle = "#444444";
rect(x - 15, y - 15, 30, 30);
}
function myMove(e){
if (dragok){
x = e.pageX - canvas.offsetLeft;
y = e.pageY - canvas.offsetTop;
}
}
function myDown(e){
if (e.pageX < x + 15 + canvas.offsetLeft && e.pageX > x - 15 +
canvas.offsetLeft && e.pageY < y + 15 + canvas.offsetTop &&
e.pageY > y -15 + canvas.offsetTop){
x = e.pageX - canvas.offsetLeft;
y = e.pageY - canvas.offsetTop;
dragok = true;
canvas.onmousemove = myMove;
}
}
function myUp(){
dragok = false;
canvas.onmousemove = null;
}
init();
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
</script>
</section>
</body>
</html>
Test
And also when i add this Code in the HEAD tag it does not work. Why is it so ?
Basic example
http://jsfiddle.net/h3BCq/1/
Prettied up one (kept rect function, added new text function, draws the bounding box for the text)
http://jsfiddle.net/h3BCq/2/
I definitely cheated. I used px for the font size to easier grab its width. You could use em's, or points though you would just need to do a conversion to pixels to get an estimated width. I Basically just removed rect, and added filltext, and modified the check to check for the text width.

Categories