I'm new to HTML5 Canvas and Javascript. I'm currently working on a HTML5 Canvas project (I wrote a separate question about it here and here).
The logic of the game is pretty simple: track the mouse position and if the mouse position exits the blue region, the game resets. The user starts on level 1 (function firstLevel). If their mouse position enters the red box region, they advance onto the next level (return secondLevel()). I did this previously by a hefty list of if statements that compared the mouse's x and y coordinates.
I've corrected the code to create a global constructor function I can reference in each of the level functions:
function Path(array, color) {
let path = new Path2D();
path.moveTo(array[0][0], array[0][1]);
for (i = 1; i < array.length; i++) {
path.lineTo(array[i][0], array[i][1]);
}
path.lineTo(array[0][0], array[0][1]);
return path;
}
And its referenced in the level functions as such:
// In the individual levels, put code that describes the shapes locally
var big = [[350,200], [900,200], [900,250], [700,250], [600,250], [600,650], [350,650]];
var blue = Path(big);
var small = [[900,200], [900,250], [850,250], [850, 200], [900, 200]];
var red = Path(small);
// 2. create cursor
c.beginPath();
c.rect(mouseX, mouseY, mouseWidth, mouseHeight);
c.fillStyle = "#928C6F";
c.fill();
// Local code that draws shapes:
c.beginPath()
c.fillStyle = '#C1EEFF';
c.fill(blue);
c.beginPath()
c.fillStyle = "#FF4000";
c.fill(red);
I want to know how can I check for the mouse position so it can run those conditional statements using the isPointInPath method? I now have a reference (refs. blue and red) for the Canvas shape, so I'm hoping there's a way I can check if the mouse's x and y position is a point that is inside the shape/path.
Link to my project: https://github.uconn.edu/pages/ssw19002/dmd-3475/final-project/maze-page-1.html
Source code: https://github.uconn.edu/ssw19002/dmd-3475/tree/master/final-project
You could use the MouseEvent.offsetX and MouseEvent.offsetY properties of a mouse event such as mousemove. (You would need to add an event listener for mouse events to the canvas element of course.)
Then use the x and y offset values obtained to call isPointInPath like
ctx.isPointInPath(path, x, y)
where path is a return value from function Path.
While MDN lists the offset properties as "experimental" they seem to have been around since IE9 at least.
Related
I'm looking for a way to make objects using mouse input on a canvas. These objects should be shaped like a parallelogram, e.g:
To detect mouse input, I am using the 'canvas.LineTo()' function. I would like to use this particular object as a hoverable link which would react (e.g. change colour) when the user hovers over the object, just like regular links do.
I would appreciate any input or alternative ideas.
Thank you for your interest.
You can use the isPointInPath method on the canvas context to check if a particular x and y coordinate is within the bounds of a Path2D object.
This means that you can maintain a list of Path2D objects that respond to a mousemove event. I've put up a fiddle here demonstrating how you can make a path respond to this event. The following snippet of code simply stores the canvas, its 2D context and the list of Path2D parallelograms you are concerned with. Following this, the parallelogram is created using a simple drawParallelogram function which creates the Path2D object, forms the shape of the parallelogram using the moveTo and lineTo methods and then returns it at the end. This object is then filled by the context and stored in the paths array so it can later be used by the canvas mousemove event.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var paths = [];
function drawParallelogram(x, y, width, height, offset) {
var path = new Path2D();
path.moveTo(x, y + offset);
path.lineTo(x, y + height);
path.lineTo(x + width, y + height - offset);
path.lineTo(x + width, y);
return path;
}
var parallelogram = drawParallelogram(50, 50, 150, 100, 30);
ctx.fill(parallelogram);
paths.push(parallelogram);
Following, this you want to add the mousemove event listener to the canvas so you can start detecting when a parallelogram has been hovered on.
canvas.addEventListener('mousemove', function (event) {
var canvas = event.target;
var ctx = canvas.getContext('2d');
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
ctx.clearRect(0, 0, canvas.width, canvas.height);
paths.forEach(function (path) {
if (ctx.isPointInPath(path, x, y)) {
// Hovered.
ctx.fillStyle = '#f00';
ctx.fill(path);
} else {
// Go back to default state.
ctx.fillStyle = '#000';
ctx.fill(path);
}
});
});
During the mousemove event listener, we retrieve the current x and y coordinate of the mouse pointer and then clear the current canvas so that any anti-aliasing artifacts are removed when drawing on top of the object again. Following this, you can iterate through each path in the paths array and check whether the current x and y coordinate is within the path. When that's the case, you know you've hovered on the item and can change its fill colour, otherwise revert it back to default settings.
One way you could improve what happens here is to create JavaScript classes for each type of hoverable item and fire some custom hover and leave event instead of manually adjusting the fill colour. This implementation would enable you to handle additional types of objects more easily.
This question already has answers here:
Zoom Canvas to Mouse Cursor
(5 answers)
Closed 8 years ago.
Is there a way to zoom on the cursor point using this code? I can't get my head around to doing it. The canvas zooms but it only zooms in and out from the top left corner.
var previousMousePosition = new Vector(0, 0);
function OnMouseWheel (event) {
var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;
var mousePosition = new Vector(event.clientX, event.clientY);
var scaleFactor = 1.1;
if (delta) {
var factor = Math.pow(scaleFactor,delta);
context.scale(factor,factor);
}
}
The canvas always scales from it current origin. The default origin is [0,0].
If you want to scale from another point, you can first do context.translate(desiredX,desiredY);. This will reset the origin of the canvas to [desiredX,desiredY].
That way your context.scale will scale from your specified origin.
Since all context transformations remain in effect for every subsequent drawing, you often want to reverse the transformations after you are done with the current drawing (=='resetting' for the next drawing which might/might not use the current transformations). To untransform, just call the transformation with negative arguments: eg. context.scale(-factor,-factor). Transformations should be done in reverse order from their original transformations.
So your refactored code could be:
// set the origin to mouse x,y
context.translate(mousePosition.x,mousePosition.y);
// scale the canvas at x,y
context.scale(factor,factor);
// ...draw stuff
// reverse the previous scale
context.scale(-factor,-factor);
// reverse the previous translate
context.translate(-mousePosition.x,-mousePosition.y);
First, I'd like to point out that from your code it looks like you're listening for 'mousewheel' events on the canvas. As noted here, the 'mousewheel' event is non-standard and is not on track to become a standard. As a result, you'll get results that are, at best, mixed when listening for it. The 'scroll' event is available on nearly every platform, and will likely be a better avenue for capturing user input.
As far as your question, you're on the right track for the behavior that you're looking for, but you're missing one step.
When you call scale on a canvas context object, the behavior is very simple. Starting at the top left corner (0,0), the method scales the points of the canvas by the factors provided. Say you have a 10x10 canvas, and a black dot at 1,1. If the canvas is scaled by a factor of 2 on both axes, the 0,0 point will stay in the same place but point 1,1 will be where the point 2,2 was before scaling.
In order to achieve the 'zooming' behavior you're looking for, the context has to be translated after scaling so that the reference point occupies the same physical position it did before the scaling. In your case, the reference point is the point where the user's cursor sits when the zoom action is performed.
Luckily, the canvas context object provides a translate(x,y) method that moves the origin of the context relative to the 0,0 point of the canvas. To translate it the correct ammount, you have to:
Calculate the distance of the mouse cursor from the canvas origin before zooming
Divide that distance by the scaling factor
Translate the origin by that value
Since your code doesn't indicate the structure of your HTML, below I've marked it up with some comments and pseudocode to show how you might implement this algorithm:
//You'll need to get a reference to your canvas in order to calculate the relative position of
//the cursor to its top-left corner, and save it to a variable that is in scope inside of your
//event handler function
var canvas = document.getElementById('id_of_your_canvas');
//We're also going to set up a little helper function for returning an object indicating
//the position of the top left corner of your canvas element (or any other element)
function getElementOrigin(el){
var boundingBox = el.getBoundingClientRect();
return { x: boundingBox.left, y: boundingbox.top};
}
function OnMouseWheel (event) {
//you probably want to prevent scrolling from happening or from bubbling so:
event.preventDefault();
event.stopPropagation();
var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;
var canvasCorner = getElementOrigin(canvas)
//JavaScript doesn't offer a 'vector' or 'point' class natively but we don't need them
var mousePosition = {x: event.clientX, y: event.clientY};
var diff = {x: mousePostion.x - canvasCorner.x, y: mousePosition.y - canvasCorner.y};
var scaleFactor = 1.1;
if (delta) {
var factor = Math.pow(scaleFactor,delta);
var transX = (-1/factor) * diff.x;
var transY = (-1/factor) * diff.y;
context.scale(factor,factor);
context.translate(transX, transY);
}
}
I'm new. I was trying to move an image but it just doesn't, I don't know where is the problem. I checked some topics but it didn't work., well here's my code:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var bgImage = new Image();
var player = new Image();
var x = 50;
var y = 50;
// We draw the background
bgImage.onload = function() {
context.drawImage(bgImage, 0, 0);
};
bgImage.src ='images/bg.png';
// We draw the player
player.onload = function(){
context.drawImage(player, x, y);
};
player.src = "images/player.png";
addEventListener("keypress", move,true);
function move(e){
if (e.keyCode == 39){
x += 2;
}
if (e.keyCode == 37){
x -= 4;
}
if (e.keyCode == 38){
y -= 4;
}
if (e.keyCode == 40){
y += 4;
}
}
You're missing the concept of the thing here, canvas is not dynamic by itself, it only provides you with the ability to draw static image frames. Therefore, the animation and interaction is possible, but it has to be entirely coded by yourself.
Any animation effect is given through the drawing of a continuous series of static frames over one another. Each frame will graphicaly represent a slight change in position/form/color of one or various objects within the scene.
It becomes clear you'd have to setup a loop chain where any change made to properties of an object in the scene would be reflected in the image frame which is currently on screen.
So it's not enough to change the value of your x and y variables, because they have been used only once in your code, to draw the first frame, then, they are used nowhere. There is nothing being drawn after the first frame. That one static image you created will remain forever on the screen, you can't expect animation out of a single static image.
Fix-wise, it's firstly necessary to setup a loading scheme for your image resources, since you need more than one. Because their loading is asynchronous AND out of order (they do not load respecting the order they were created).
After everything is loaded, you can choose to either fire a continuous loop, which will draw another frame regardless of wheter any change has actually been made to anything on the screen (this is the standard way for making animations, in more complex applications, it will allow you to keep control of animation time)
Or, since your code is simple enough, you could choose to draw another frame when, and only when, the position of your object has been changed. Something like this
addEventListener("keypress", move,true);
function move(e)
{
//... key handling stuff
redraw(); // Drawing the next, modified, frame
}
function redraw()
{
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(bgImage, 0, 0);
context.drawImage(player, x, y);
}
I have an application with many draggable objects that can also be rotated in 90 degree increments. I'm trying to figure out how to stop the user from dragging the objects outside the Raphael paper (canvas).
This is fairly simple for unrotated objects. I can simply see if the current x and y coordinates are less than 0 and set them to 0 instead. I can adjust similarly by checking if they are outside the canvas width and height.
However, a problem arises when the object is rotated because for some odd reason the coordinate plane rotates as well. Is there an easy way to keep objects inside the canvas? Or is there an example of some this somewhere?
I have spent many hours fiddling with this and I can't seem to make sense of the rotated coordinate plane in order to adjust my calculations. Even when debugging the current coordinates, they seem to shift oddly if I drag an object, release it, and then drag the object again.
Any help is greatly appreciated.
Thanks,
Ryan
I had a similar problem, I needed to move a shape within the boundaries of another shape, so what I did was:
element.drag(onstart, onmove, onend);
...
onStart: function(x,y,e){
// Initialize values so it doesn't recalculate per iteration
// this allows to resume dragging from the point it were left
App.oldX = 0;
App.oldY = 0;
App.currentCircleX = App.fingerPath.attr('cx');
App.currentCircleY = App.fingerPath.attr('cy');
},
onMove: function(dx,dy,x,y,e){
App.setDirection(dx,dy);
},
onEnd: function(e){
// nothing to do here for now
},
// this function tells the element to move only if it's within the bound area
setDirection: function(dx, dy){
var isXYinside;
this.newX = this.currentCircleX - (this.oldX - dx);
this.newY = this.currentCircleY - (this.oldY - dy);
// HERE is the key, this method receives your bounding path and evaluates the positions given and then returns true or false
isXYinside = Raphael.isPointInsidePath(this.viewportPath, this.newX, this.newY);
this.oldX = dx;
this.oldY = dy;
// so if it is within the bound area, will move, otherwise will just stay there
if (isXYinside) {
this.fingerPath.attr({
"cx": this.newX,
"cy": this.newY
});
this.currentCircleX = this.newX;
this.currentCircleY = this.newY;
}
}
I know this is an old one, but I stumbled upon this question when trying to figure out a way to do it. So here's my 2 cents in case someone has this problem.
Reference:
Raphael.isPointInsidePath
Have you tried Element.getBBox()
There Are 2 flavones which give the result before rotation and after rotation
You should toggle the Boolean argument and test it
Something like I am have:
function mouseClick(event) {
...
}
canvas.addEventListener("mousedown", mouseClick, false);
function drawRect(x, y) {
context.fillRect(x, y, 16, 16);
};
drawRect(10, 10);
How to do something like if I am click on my Rect in Canvas get something? something like If I am click on Rect get alert;
Sorry for my English language.
A canvas is nothing more than a bitmap. There is no record kept that there was a rectangle drawn on the canvas, so if you want to detect that the click was inside an area that you drew a rectangle on, you have to keep a record of the areas you've drawn and test against them. eg:
var rects= [];
function mouseClick(event) {
// Get position of click relative to canvas. This is not reliable! Requires
// standards mode, and the canvas not being nested in other offsetParents.
// Getting page-relative co-ordinates reliably in all cases is outside the
// scope of this question...
//
var x= event.clientX-document.documentElement.scrollLeft-canvas.offsetLeft;
var y= event.clientY-document.documentElement.scrollTop-canvas.offsetTop;
// Hit-test each rectangle in the list of those drawn
//
for (var i= rects.length; i-->0;) {
var x0= rects[i][0], y0= rects[i][1], x1= rects[i][2], y1= rects[i][3];
if (x0<=x && x<x1 && y0<=y && y<y1) {
alert('you clicked on a rectangle!');
}
}
}
function drawRect(x, y) {
rects.push([x, y, x+16, y+16])
context.fillRect(x, y, 16, 16);
};
drawRect(10, 10);
If you are doing a lot of this sort of thing you are likely to be better off using a retained-mode drawing system like SVG instead of the pure-bitmap Canvas. In SVG you can listen for click events directly on a rectangle object, move the rectangle, re-stack it and so on.
$(canvas).mousedown(function myDown(e)
{
var position = $(canvas).position();
var x = e.pageX-position.left;
var y = e.pageY-position.top;
...
});
This is much better way to do this!
I think what you're saying is that you want events to occur when you click on objects you have drawn on a canvas. I have written a few tutorials on how to make persistent shapes and move them around with mouse clicks. That should give you a good starting point for this.
You could also try out Concrete.js http://www.concretejs.com which is a lightweight canvas framework that adds interactivity. You would just do something like this:
var key = canvas.getIntersection(x,y);
if x,y intersects a graphic you've drawn with a given key, it returns the key, and you know what was hovered/clicked on.
This is a much better solution because it will work on anything you draw, including circles, lines, curves, polygons, arbitrary blobs, etc.
addEventListener is tricky. What I would try is
canvas.addEventListener.apply(canvas, ["mousedown", mouseClick, false]);
or use an anonymous function.
canvas.addEventListener.apply(canvas, ["mousedown", function(){ mouseClick(); }, false]);
Using the apply function can help make sure the eventListener is applied to the right element. You can read about it here.