I have currently two circles in a <canvas> tag with HTML5 & JavaScript.
Now I'm trying to add an image (done) that changes based on mouse-over and click.
It's basically an implementation of a play / pause button with an extra color change when the user mouse-overs the button.
I can't seem to figure out how events work on shapes in HTML5 since they are not objects ... Here is my code at the moment :
window.onload = function() {
var canvas = document.getElementsByTagName('canvas')[0];
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
//Outer circle
context.beginPath();
context.arc(centerX, centerY, 150, 0, Math.PI * 2, false);
context.fillStyle = "#000";
context.fill();
context.stroke();
//Inner cicle
context.beginPath();
context.arc(centerX, centerY, 75, 0, Math.PI * 2, false);
context.fillStyle = "#fff";
context.fill();
context.stroke();
//Play / Pause button
var imagePlay = new Image();
var imageHeight = 48/2;
imagePlay.onload = function() {
context.drawImage(imagePlay, centerX - imageHeight, centerY - imageHeight);
};
imagePlay.src = "images/play.gif";
}
How to handle events on shapes created with <canvas>?
How to clean-up / remove images on the <canvas> when replacing it with another one?
There is technically no way to register mouse events on canvas-drawn shapes. However, if you use a library, like Raphael (http://raphaeljs.com/), it can keep track of shape positions and thus figure out what shape is receiving the mouse event. here's an example:
var circle = r.circle(50, 50, 40);
circle.attr({fill: "red"});
circle.mouseover(function (event) {
this.attr({fill: "red"});
});
As you can see, it's very simple this way. For modifying shapes, this library will also come in handy. Without it you would need to remember how to redraw everything each time you make a change
Well The simple answer is you can't. You either will have to find the coordinates of the click event and calculate whether you want to perform an option or not or you can use area and map tags and overlay the canvas element with it. To change a canvas use the clearRect function to draw paint a rectangle over everything and then redraw what you want.
There is no "built-in" way of keeping track of shapes drawn on the canvas. They are not treated as objects, but rather just pixels in an area. If you want to handle events on shapes drawn on the canvas, you would need to keep track of the area each shape covers, and then determine which shape you're triggering the event for based on the mouse position.
You can just draw over other shapes if you want to replace it with something. You might want to take a look at globalCompositeOperation.
If you want to treat your drawings as objects, I would recommend using SVG instead of canvas.
Another option is to use buttons, and then style them using CSS.
Basically, what you're doing now really wasn't the intended purpose or use of the canvas. It's like using a pencil to hammer in nails - you're using the wrong tool for the job.
While it's true that you cannot create click events for objects drawn on the canvas there is a workaround: Wrap the canvas in a DIV tag and then add the images within the DIV tag above the CANVAS tag.
<div id="wrapper">
<img src="img1.jpg" id="img1"></img>
<canvas id="thecanvas"></canvas>
</div>
Then use CSS to make the images position:absolute and use left:*px and top:*px attributes to place the image over the canvas where you would have normally drawn it.
#img1{
position:absolute;
left: 10px;
top: 10px;
}
You can then add click events to the image which is placed over the canvas giving the impression that you are clicking on the canvas(the below example uses the jQuery click() function)
$( "#img1" ).click(function(){
alert("Thanks for clicking me");
});
You can cast a ray into the canvas and manually test your images for intersection with the ray. You should look at it like you press, and send a ray into the screen. You should write a
objectsUnderPoint( x, y );
function that returns an array of all the images that intersect with the ray at x, y.
This is the only real right answer, and this is how it is usually done in 3D engines as well.
Related
What I would like to do is draw a circle on a canvas and then when a function is called delete the previous circle and draw a new one. Is this possible without having to redraw the whole canvas?
Take the code below for example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(95, 50, 40, 0, 2 * Math.PI);
ctx.stroke();
A canvas is a collection of pixels (it's exactly like an image, however it's computed from code instead of being just loaded from file).
The only way to remove the old circle and draw the new one (if you want pixel perfect result) is repainting everything with the circle in the new position.
A possible solution could be using three distinct canvas elements layered one on top of the other:
whatever is behind the changing parts
the changing parts
whatever is covering the changing parts
1 and 3 can be kept fixed, you need to redraw only the second canvas.
I am creating a web-based annotation application for annotating images via the HTML canvas element and Javascript. I would like the user to mouse down to indicate the start of the rectangle, drag to the desired end coordinate and let go to indicate the opposite end of the rectangle.
Currently, I am able to take the starting coordinates and end coordinates to create a rectangle on the image with the context.rects() function, however as I am uncertain on how to resize a specific rectangle on the canvas, that leaves me with the rectangle only being drawn after the user has released the mouse click.
How would I be able to resize a specific rectangle created onmousedown while dragging?
The following is the code snippet that performs the function:
var isMouseDown = false;
var startX;
var startY;
canvas.onmousedown = function(e) {
if(annMode){
isMouseDown = true;
var offset = $(this).offset();
startX = parseInt(e.pageX - offset.left);
startY = parseInt(e.pageY - offset.top);
}
};
canvas.onmousemove = function(e) {
if(isMouseDown) {
var offset = $(this).offset();
var intermediateX = parseInt(e.pageX - offset.left);
var intermediateY = parseInt(e.pageY - offset.top);
console.log(intermediateX);
}
};
canvas.onmouseup = function(e) {
if(annMode&&isMouseDown){
isMouseDown = true;
var offset = $(this).offset();
var endX = parseInt(e.pageX - offset.left);
var endY = parseInt(e.pageY - offset.top);
var width = endX - startX;
var height = endY - startY;
context.strokeStyle = "#FF0000";
context.rect(startX, startY, width, height);
context.stroke();
}
isMouseDown = false
};
Here my handy-front-end scripts come in handy!
As I understood the question, you wanted to be able to move your mouse to any point on the canvas, hold the left mouse button, and drag in any direction to make a rectangle between the starting point and any new mouse position. And when you release the mouse button it will stay.
Scripts that will help you accomplish what you are trying to do:
https://github.com/GustavGenberg/handy-front-end/blob/master/README.md#canvasjs
https://github.com/GustavGenberg/handy-front-end/blob/master/README.md#pointerjs
Both scripts just makes the code a lot cleaner and easier to understand, so I used those.
Here is a fiddle as simple as you can make it really using
const canvas = new Canvas([]);
and
const mouse = new Pointer();
https://jsfiddle.net/0y8cbao3/
Did I understand your question correctly?
Do you want a version with comments describing every line and what is does?
There are still some bugs at the moment but im going to fix those soon!
EDIT
After reading your questions again, I reacted to: "...however as I am uncertain on how to resize a specific rectangle on the canvas...".
Canvas is like an image. Once you have drawn to it, you can NOT "resize" different shapes. You can only clear the whole canvas and start over (ofcourse you can clear small portions too).
That's why the Canvas helper is so helpful. To be able to "animate" the canvas, you have to create a loop that redraws the canvas with a new frame each 16ms (60 fps).
The canvas API does not preserve references to specific shapes drawn with it (unlike SVG). The canvas API simply provides convenient functions to apply operations to the individual pixels of the canvas element.
You have a couple options to achieve a draggable rectangle:
You can position a styled div over your canvas while the user is dragging. Create a container for your canvas and the div, and update the position and size the div. When the user releases, draw your rectangle. Your container needs to have position: relative and the div needs to be absolutely positioned. Ensure the div has a higher z-index than the canvas.
In your mouse down method, set div.style.display to block. Then update the position (style.left, style.top, style.width, and style.height) as the mouse is dragged. When the mouse is released, hide it again (style.display = 'none').
You can manually store references to each item you want to draw, clear the canvas (context.clearRect), and redraw each item on the canvas each frame. This kind of setup is usually achieved through recursive usage of the window.requestAnimationFrame method. This method takes a callback and executes on the next draw cycle of the browser.
The first option is probably easier to achieve in your case. If you plan to expand the capabilities of your app further, the 2nd will provide more versatility. A basic loop would be implemented as so:
// setup code, create canvas & context
function mainLoop() {
context.clearRect(0, 0, canvas.width, canvas.height);
/** do your logic here and re-draw **/
requestAnimationFrame(mainLoop);
}
function startApp() {
requestAnimationFrame(mainLoop)
}
This tutorial has detailed explanation of event loops for HTML canvas: http://www.isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing
I also have a fully featured implementation on my GitHub that's part of rendering engine I wrote: https://github.com/thunder033/mallet/blob/master/src/mallet/webgl/webgl-app.ts#L115
I'm having a bit of trouble here to develop this functionality since it must work on IE9+ so css clip-path is not an option ( http://caniuse.com/#feat=css-clip-path ).
The issue:
I need to create a grid composed of 6 elements.
Each element is an image.
The images can be different according to user answers before getting to the grid page.
Eeach element / image must be clicable and will acquire a "selected" class that will overlay div with text and background image.
image:
What is the best way to achieve this?
One way to do this could be to save out each combination of the six images you require into one big image. Then, depending on the user's answer combination, you insert the corresponding image as a background-image of a div. You then overlay click-able hotspots within the same div that roughly correlate to the dividing edges.
This may however not be the most practical solution and largely depends on how many answers/images you are dealing with.
Alternatively you could draw SVG shapes and set their fills to the images you require.
I can recommend Raphael.js as a starting point. You should be able to find what you need in the documentation
Another option would be to use HTML5 canvas:
http://jsfiddle.net/julienbidoret/GKP7X/1/
(credit goes to julienbidoret for the jsfiddle)
Javascript:
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = document.createElement('IMG');
img.onload = function () {
ctx.save();
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(240, 0);
ctx.lineTo(220, 240);
ctx.lineTo(0, 240);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/2/2b/Clouds.JPG";
HTML:
<canvas id="c" width="300" height="300" ></canvas>
Both SVG and canvas are supported in IE9.
I want to give a div, which containing a canvas, visual controllers when resizing it with jQuery's resizable() function.
I mean by saying 'visual controllers' these 8 black squares which appear when you click on an image that allow you to resize the image like this example :
I have written the following function that draws 8 squares around the div. When clicking on the div, it gives the aimed visual appearance. When clicking on the div again it removes the 8 squared and removes the resizable() function. It works fine but when resizing the div and clicking on it again to remove the 8 squares it doesn't remove them.
I need to save the canvas state before clicking on before it applies the drawings and restore it when clicking on the canvas again.
$(document).on("click", "canvas", function(eg) {
var thisCanvas = $(this).attr("id");
var theCanvas = document.getElementById(thisCanvas);
var ctx = theCanvas.getContext("2d");
canvasSelected(thisCanvas, ctx);
});
When the user clicks of the canvas it fires the following function:
function canvasSelected(theCanvas, ctx){
var ctxWidth = $("#"+theCanvas).width();
var ctxHeight = $("#"+theCanvas).height();
if($("#"+theCanvas).hasClass("bordering")){
ctx.restore();
$("#"+theCanvas).addClass("defaultBorder");
$("#"+theCanvas).removeClass("bordering");
ctx.beginPath();
ctx.clearRect(0,0,6,6);
ctx.clearRect(ctxWidth- 6,0,6,6);
ctx.clearRect(ctxWidth/2,0,6,6);
ctx.clearRect(0,ctxHeight- 6,6,6);
ctx.clearRect(ctxWidth- 6,ctxHeight- 6,6,6);
ctx.clearRect(ctxWidth/2,ctxHeight- 6,6,6);
ctx.clearRect(0,ctxHeight/2,6,6);
ctx.clearRect(ctxWidth- 6,ctxHeight/2,6,6);
$("#"+theCanvas).resizable("option", "disabled", true);
}else{
ctx.save();
$("#"+theCanvas).removeClass("defaultBorder");
$("#"+theCanvas).addClass("bordering");
ctx.beginPath();
ctx.fillStyle = "black";
ctx.fillRect(0,0,6,6);
ctx.fill();
ctx.fillRect(ctxWidth- 6,0,6,6);
ctx.fill();
ctx.fillRect(ctxWidth/2,0,6,6);
ctx.fill();
// bottom rects..
ctx.fillRect(0,ctxHeight- 6,6,6);
ctx.fill();
ctx.fillRect(ctxWidth- 6,ctxHeight- 6,6,6);
ctx.fill();
ctx.fillRect(ctxWidth/2,ctxHeight- 6,6,6);
ctx.fill();
// middle rects
ctx.fillRect(0,ctxHeight/2,6,6);
ctx.fill();
ctx.fillRect(ctxWidth- 6,ctxHeight/2,6,6);
ctx.fill();
$("#"+theCanvas).resizable();
$("#"+theCanvas).resizable("option", "disabled", false);
}
}
here is the jsfiddle
Your problem is how the canvas is being re-sized via resize(). The size is being changed in terms of how large the canvas is, but it will not change the size of the coordinate system. Your initial width and height of 550x350 stay the same.
Live Demo
All I did was add the following to your canvasSelected event,
// get the canvas element
var canvas = document.getElementById(theCanvas);
// change the pixel dimensions to match the css width and height.
canvas.width = ctxWidth;
canvas.height = ctxHeight;
This ensures the pixel dimensions will be updated as well. Just remember its usually a bad idea to re-size the canvas element using CSS as you will get unexpected results.
The above will cause you to have to redraw your graphics however. So another method is just to keep track of the original width, and height, of your canvas and use those values like the following fiddle does.
Live Demo 2
In this example I just made height and width global so they would always be referenced, however I imagine you can use the demo provided to come up with a better way of keeping track of the original height and width of the element.
Also note, jQuery's width() and height() are not the same as calling width and height on the canvas element. jQuery's methods re-size the element using its style properties.
I have this bound to the mousemove event of my canvas:
function(e){
var contDiv = $('#current_system_map');
var offset = contDiv.offset();
x = e.clientX-offset.left;
y = e.clientY-offset.top;
context.beginPath();
context.moveTo(0,y);
context.lineTo(595,y);
context.moveTo(x,0);
context.lineTo(x,595);
context.strokeStyle = "rgb(255,255,255)";
context.stroke();
}
and it works fine, to a point. The drawn cross is persistent, so when the mouse moves a new cross is drawn but the old one remains. I've tried essentially re-drawing the canvas but that cause the cross to be laggy and remain quite away behind the mouse.
So i need to know how to draw the cross and make it dis-appear without having to re-draw everything on the canvas
http://jsfiddle.net/PgKEt/2/
This is the best that I can do.
If you try to use setInterval and such to animate it, it will keep redrawing even when it does not need to. So by doing this, you essentially only redraw when the mouse moves, and only draw 2 lines, instead of whatever content you want it on top.
In addition, if you have any detection such as mousedown and such, it has to be on whatever canvas is on the top, otherwise it will not detect them anymore.
Usually if you draw something on the canvas you will have to redrawn the canvas contents to erase it. I suggest you use an image element as a cursor and position it absolutely above the
Or you could try the old trick and draw the cursor in the canvas with globalCompositeOperation='xor', then draw it again in the same place to erase it. Afterwards you will need to restore globalCompositeOperation to source-over.
This approach works fast enough for me in Firefox 3.6.8 to do in a mousemove event. Save the image before you draw the crosshair and then restore it to erase:
To save:
savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")
The to restore:
ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)
If you do not want to store it persistently, you can also take a look at SVG.
Try
ctx.clearRect(0,0,YourCanvasHeight,YourCanvasWidth);
In my case I implemented a circle and every time the user clicks inside it, this instruction returns and deletes the previous points.
This is the complete code:
function getMousePosition(canvas, event) {
let rect = canvas.getBoundingClientRect();
let x = event.offsetX; //event.clientX - rect.left;
let y = event.offsetY; //event.clientY - rect.top;
drawPoint(canvas,x,y);
};
function drawPoint(canvas,x,y) {
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.clearRect(0,0,200,200);
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}
};
$(document).ready(function(){
let canvasElem = document.getElementById("circle");
canvasElem.addEventListener("click", function(e)
{
getMousePosition(canvasElem, e);
});
});