I found this code on this site, but as I'm a novice with .js, I can't get my head around what the best practice would be to return a square to its original colour. ie, click on a square it changes color, click again it goes back to what it was.
Do I
just color the square on a second click? or
put an else if statement somewhere?
<script>
function getSquare(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: 1 + (evt.clientX - rect.left) - (evt.clientX - rect.left)%10,
y: 1 + (evt.clientY - rect.top) - (evt.clientY - rect.top)%10
};
}
function drawGrid(context) {
for (var x = 0.5; x < 10001; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 10000);
}
for (var y = 0.5; y < 10001; y += 10) {
context.moveTo(0, y);
context.lineTo(10000, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
function fillSquare(context, x, y){
context.fillStyle = "red";
context.fillRect(x,y,9,9);
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
drawGrid(context);
canvas.addEventListener('click', function(evt) {
var mousePos = getSquare(canvas, evt);
fillSquare(context, mousePos.x, mousePos.y);
}, false);
</script>
First of all it's a bit dangerous to draw over a previous state, because sometimes you get 'ghosting'. but ignores this for now
And to get back to your questions, as nearly always it depends on the situation.
If you have a canvas that only changes when a user interacts you can (should) use an event handler to trigger a draw.
When you have other things animated you should split animation and values. (requestAnimationFrame) This does however require more work since everything should be stored as a variable.
You are going to need an if statement in the draw-code becaus you need to determine when it's colored and when not.
//https://jsfiddle.net/dgq9agy9/2/
function fillSquare(context, x, y){
var imgd = context.getImageData(x, y, 1, 1);
var pix = imgd.data;
console.log("rgba="+pix[0]+"-"+pix[1]+"-"+pix[2]+"-"+pix[3]);
if(pix[0]==0){
context.fillStyle = "red";
}else if(pix[0]==255){
context.fillStyle = "black";}
context.fillRect(x,y,9,9);
}
In my example I check if the pixel you clicked is red or black and switch them.
This is an easy solution because you don't need any extra variables, but it's not very clean.
If you want a clean solution, you could create an 2d-array with 1 or 0 values and draw a color depending on the number. But this requires alot/more work. Like translation the X,Y mouse click to the N-th number of square.(something like this: color_arr[x/9][y/9])
Related
I have been struggling with this for a very, very long time (6 months plus). There have been some partial answers to my question but I have been unable to put the available asnswer-bits together to be able to do anything useful with it. This code will be an amazing tool for all new aspiring simple canvas game developers and will greatly benefit the community for sure.
-I need to create a very large scrollable canvas (scrolled by holding right mouse button), similar to this: https://www.desmos.com/calculator , say 50k px by 50k px and this (size)should be amendable in code.
-When we scroll, the background moves and all items, of course, will need to move with this scroll.
-There have to be some measure of scroll rate in code, which is should be amendable.
-Rendering structure need to remain as in my codepen - simple constructor objects generated on screen then rendered using request animation frame. https://codepen.io/alexhermanuk/pen/bGGXLZG
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,this.y,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<10; i++){
squares.push(new Square(width*Math.random(),height*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
squares.forEach(Square => {Square.update(squares)})
requestAnimationFrame(animateEverything);
}
animateEverything();
}
-Canvas / scrollable area need to be full screen without overflows
-Canvas background will consist of 4 virtually identical square images (2 main images and 2 mirror images) and these images are rendered/ replicated as a background as we scroll left right up or down and will create an unlimited background image (in our case it is just a wavy ocean). So, these have to be logically rendered like a tile map of some sort... There is no easy way to provide sample images, so please, bring yours along.
I appreciate YOUR effort and thank all in advance.
Here you go. It scrolls with 'right click' drag.
I wasn't sure what you meant by scroll rate. Do you want the scroll rate displayed, or controlled? Anyways, I did both to be safe. scrollRate controls have fast it scrolls, and it defaults to 1. scrollMeter is a number that loosely represents how fast the canvas is being scrolled. It is printed in the top left corner of the canvas.
And about tiling, I made a BackgoundTile class. You can create 4 different tiles with different x, y, and x_gap, y_gap to have what you want. But if you care about performance, instead of making 4 tile objects, use just one with a combined image. Use an image editor to combine the 4 squares to make one big square if possible. Tiling is almost always a performance heavy job, and a bit of optimization goes a long way.
I also added a window/canvas resize handler, so the aspect ratio doesn't go haywire. This also prevents the canvas from going low-res when you enlarge the window/canvas.
I left animateEverything as is, but I'd recommend adding more control to rendering instead of recursively calling it indefinitely.
I hope you like this demo.
CodePen
paste below on in this link https://codepen.io/alexhermanuk/pen/bGGXLZG.
I have added vertical scroll only
you can implement horizontal in same way.
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
maxWidth = 15000,
maxHeight = 15000;
var origin = { x: 0, y: 0},
scrollY = 0,
isMouseDownOn = '',
scrollBarWidth = 25,
scrollThumbHeight = 45,
navButtonSize = 25,
totalScrollHeight = height-navButtonSize*2-scrollThumbHeight;
canvas.removeEventListener('mousedown', onMouseDown);
canvas.removeEventListener('mousemove', onMouseMove);
canvas.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mouseup', onMouseUp);
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseup', onMouseUp);
window.addEventListener('mouseup', onMouseUp);
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
var newY = this.y - origin.y;
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,newY,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<1000; i++){
squares.push(new Square(width*Math.random(),maxHeight*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
console.log(' m here')
squares.forEach(Square => {
Square.update(squares)
})
drawScrollbar();
drawOrigin ();
//requestAnimationFrame(animateEverything);
}
animateEverything();
function drawScrollbar() {
var currentScroll = scrollY+navButtonSize;
context.save();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),0,navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'green';
context.fillRect((width-navButtonSize ),(navButtonSize),scrollBarWidth,(height-navButtonSize*2));
context.closePath();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),(height-navButtonSize),navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'yellow';
context.fillRect((width-navButtonSize),currentScroll,scrollBarWidth,scrollThumbHeight);
context.closePath();
context.restore();
}
function onMouseDown(e) {
//console.log('onMouseDown ' , e);
detectElement(e);
}
function onMouseMove(e) {
if(isMouseDownOn === 'scrollY') {
var posY = e.pageY - canvas.offsetTop;
scrollY = Math.min( (height-navButtonSize*2-scrollThumbHeight), Math.max(posY, 0) ) ;
origin.y = (scrollY/(height-navButtonSize*2-scrollThumbHeight))*maxHeight;
animateEverything();
}
}
function onMouseUp(e) {
//console.log('onMouseUp ' , e);
isMouseDownOn = '';
}
function drawOrigin () {
context.save();
context.beginPath();
context.font = "18px Arial";
context.fillText("Origin X:"+origin.x, 10, 30);
context.fillText("Origin Y:"+origin.y, 10, 50);
context.closePath();
context.restore();
}
function detectElement(e) {
isMouseDownOn = '';
var posX = e.pageX- canvas.offsetLeft;
var posY = e.pageY- canvas.offsetTop;
var currentThumbPos = scrollY+navButtonSize;
if(posX >= width-scrollBarWidth && (posY >= currentThumbPos && posY <= currentThumbPos+scrollThumbHeight) ) {
isMouseDownOn = 'scrollY';
}
}
}
I have drawn a circle with canvas using following code.
<div class="abc"><canvas id="myCanvas">HTML5 canvas tag.</canvas></div>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100,75,50,-0.1*Math.PI,1.7*Math.PI);
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke();
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
ctx2.beginPath();
ctx2.arc(100,75,50,1.7*Math.PI,-0.1*Math.PI);
ctx2.lineWidth = 15;
ctx2.strokeStyle = "#a9a9a9";
ctx2.stroke();
</script>
Here is the result on the web browser.
Now I need to call two different javascript function when the user clicks on red part and gray part of the circle.
On click of the red part it should call function1 and on click of gray part, it should call function2.
I've tried a lot but didn't succeed. I need some expert's help to implement it.
Letting the browser do (most of) the work
On click of the red part it should call function1 and on click of gray part, it should call function2.
You can use reusable path objects to store the different paths you want to test against and then use isPointInPath() with path and coordinates as arguments.
By checking each path for a hit you can assign a different call based on for example index.
And there is no need to do this:
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
This will simply reference the same context as a canvas can only have one - if requested more than once the same will be given (or null if different type).
How to use multiple click event on a canvas
You can share the click handler to do what you want as shown below -
For modern browsers you can use both Path2D objects to store path information you want to use later (I'll address older browsers in the second example).
Example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var p1 = new Path2D();
var p2 = new Path2D();
var paths = [p1, p2]; // store paths in array for check later
// store arc parts to respective path objects
p1.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
p2.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
// render the two path objects using a common context, but different style
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke(p1);
ctx.strokeStyle = "#a9a9a9";
ctx.stroke(p2);
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// iterate through path array to test each path for hits
for(var i = 0; i < paths.length; i++) {
if (ctx.isPointInStroke(paths[i], x, y)) { // check which path
console.log("Path " + (i+1) + " clicked");
break;
}
}
};
<canvas id="myCanvas"></canvas>
Browser compatibility
Older browsers however, won't support the isPointInStroke(), such as IE11.
For this scenario you can use simple math to figure out if the click is within the radius of the arcs:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 15;
ctx.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
ctx.strokeStyle = "#c32020";
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
ctx.strokeStyle = "#a9a9a9";
ctx.stroke();
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top,
diffX = 100 - x,
diffY = 75 - y,
len = diffX*diffX + diffY*diffY, // we don't need to square-root it:
r1 = 43*43, // it's faster to scale up radius (50-50% of linewidth)
r2 = 57*57; // radius + 50% of linewidth
// are we on the edge of the circle?
if (len >= r1 && len <= r2) {
// now find angle to see if we're in red or grey area
var angle = Math.atan2(diffY, diffX);
if (angle > 0.7 * Math.PI && angle < 0.9 * Math.PI) {
console.log("Grey part");
}
else {
console.log("Red part");
}
}
};
<canvas id="myCanvas"></canvas>
Note that a special case in the latter example, i.e. when the arc crosses the 0/360° point, you will want to split the checks [0, angle> and [angle, 360>.
You will need to add a click event to the canvas element and calculate if the red or gray is clicked.
Here's some code to get you started, but you'll still have to figure out what coordinates your circle area has.
var elem = document.getElementById('myCanvas'),
elemLeft = elem.offsetLeft,
elemTop = elem.offsetTop;
// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
var x = event.pageX,
y = event.pageY;
// use x and y to determine if the colored regions are clicked and execute code accordingly
}, false);
Code gotten from here: How do I add a simple onClick event handler to a canvas element?
I created circles using canvas but can't delete the same on double click.`
var canvas,
context, shapes,
dragging = false, draggingtoMove = false,
dragStartLocation,dragEndLocation,
snapshot;
var numShapes;
function initiate() {
numShapes = 100;
shapes = [];
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
canvas.addEventListener('dblclick', dblclickerase);
}
function dblclickerase(evt)
{
var i;
//We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
//only the topmost one will be dragged.
var highestIndex = -1;
//getting mouse position correctly, being mindful of resizing that may have occured in the browser:
var bRect = canvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left)*(canvas.width/bRect.width);
mouseY = (evt.clientY - bRect.top)*(canvas.height/bRect.height);
//find which shape was clicked
for (i=0; i < shapes.length; i++) {
if (hitTest(shapes[i], mouseX, mouseY)) {
//draggingtoMove = true;
if (i > highestIndex) {
// here i want to delete the circle on double click but not getting logic, I can get mouse location but how to select the circle and delete it
}
}
}
}
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {
x: x,
y: y
};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function draw(position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
var i=0;
var tempX;
var tempY;
var tempRad;
var tempR;
var tempG;
var tempB;
var tempColor;
tempRad = radius;
tempX = dragStartLocation.x;
tempY = dragStartLocation.y;
tempColor = getRndColor();
tempShape = {x:tempX, y:tempY, rad:tempRad, color:tempColor};
shapes.push(tempShape);
context.beginPath();
context.arc(tempX, tempY, tempRad, 0, 2*Math.PI, false);
//context.closePath();
context.fillStyle = tempColor;
context.fill();
i++;
}
function dragStart(evt) {
dragging = true;
//if (dragging == true) {
dragStartLocation = getCanvasCoordinates(evt);
takeSnapshot();
//}
}
function hitTest(shape,mx,my) {
var dx;
var dy;
dx = mx - shape.x;
dy = my - shape.y;
//a "hit" will be registered if the distance away from the center is less than the radius of the circular object
return (dx*dx + dy*dy < shape.rad*shape.rad);
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
draw(position);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
dragEndLocation = getCanvasCoordinates(event);
draw(position);
}
function getRndColor() {
var r = 255 * Math.random() | 0,
g = 255 * Math.random() | 0,
b = 255 * Math.random() | 0;
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
function eraseCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
addEventListener("load",initiate);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<canvas id="canvas" width="1020" height="640"></canvas>
<button onclick="eraseCanvas()" style="float: right;">Reset</button>
</body>
</html>
My question is how to delete the circle when double click on same, I
added 'dblClick' eventListener but still I am only able to perform the 'clearRect'
which will only clear the rectangle from start and end location which is little odd. Another thing I can't change the color to white which will not be valid.point as if my circle overlaps another will look odd.
You can't delete what you draw on the canvas like that. Once it's drawn on the canvas, it stays there and you have no way to access it except to read the pixel data - but that won't solve your problem because you can have overlapping circles of the same color.
To solve the issue, you must keep track of drawn circles, and redraw the full canvas every time it's needed (when adding a new circle, removing an old one, etc.). That way, when you want to delete a circle, you'd simply remove it from the list of circles (a simple array would work). But the important thing is that you need to clear and redraw the full canvas.
Summary: while having your canvas constantly redrawn (either on every tick or when a user interaction happens), your click'n'drag function should only be adding the circle to the circle list (specifying data like x, y, radius, color), while double-clicking a circle would look up that circle in the list, and remove it.
I am new to Javascript and i am in the process of making a project web based (HTML)
With my basic knowledge i have managed to create a form and drawn a rectangle on it.
I would now like to be able to click the rectangle , using it like a button but I cannot seem to find any tutorials or answers that can help me.
This is the code for my rectangle :
function Playbutton(top, left, width, height, lWidth, fillColor, lineColor) {
context.beginPath();
context.rect(250, 350, 200, 100);
context.fillStyle = '#FFFFFF';
context.fillStyle = 'rgba(225,225,225,0.5)';
context.fillRect(25,72,32,32);
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#000000';
context.stroke();
context.closePath();
context.font = '40pt Kremlin Pro Web';
context.fillStyle = '#000000';
context.fillText('Start', 345, 415);
}
I am aware that you need to find the x,y coordinates and mouse position in order to click in the area of the rectangle. But i'm really stuck at this point.
It maybe really simple and logic, but we have all had to go past this stage.
You were thinking in the right direction.
You can solve this multiple ways like using html button suggested in the comments.
But if you do need to handle click events inside your canvas you can do something like this:
Add a click handler to the canvas and when the mouse pointer is inside your bounding rectangle you can fire your click function:
//Function to get the mouse position
function getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
//Function to check whether a point is inside a rectangle
function isInside(pos, rect){
return pos.x > rect.x && pos.x < rect.x+rect.width && pos.y < rect.y+rect.height && pos.y > rect.y
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
//The rectangle should have x,y,width,height properties
var rect = {
x:250,
y:350,
width:200,
height:100
};
//Binding the click event on the canvas
canvas.addEventListener('click', function(evt) {
var mousePos = getMousePos(canvas, evt);
if (isInside(mousePos,rect)) {
alert('clicked inside rect');
}else{
alert('clicked outside rect');
}
}, false);
jsFiddle
Path2d may be of interest, though it's experimental:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
Basically, you'd do all of your drawing into a Path, and use the .isPointInPath method to do the checking. For a rectangle like you're describing, you can do that math pretty simply, but the glory of this is you can have a complex path set up, and it will do the collision detection for you:
//get canvas/context
const canvas = document.getElementById("myCanvas")
const context = canvas.getContext("2d")
//create your shape data in a Path2D object
const path = new Path2D()
path.rect(250, 350, 200, 100)
path.rect(25,72,32,32)
path.closePath()
//draw your shape data to the context
context.fillStyle = "#FFFFFF"
context.fillStyle = "rgba(225,225,225,0.5)"
context.fill(path)
context.lineWidth = 2
context.strokeStyle = "#000000"
context.stroke(path)
function getXY(canvas, event){ //adjust mouse click to canvas coordinates
const rect = canvas.getBoundingClientRect()
const y = event.clientY - rect.top
const x = event.clientX - rect.left
return {x:x, y:y}
}
document.addEventListener("click", function (e) {
const XY = getXY(canvas, e)
//use the shape data to determine if there is a collision
if(context.isPointInPath(path, XY.x, XY.y)) {
// Do Something with the click
alert("clicked in rectangle")
}
}, false)
jsFiddle
this is my first post and I'm having a little trouble trying to finish my code, I have the basics done, but need some guidance: anyone willing to help?
Here's what I'm attempting to do, but struggling with: I've come to a stand still!
Add a keyboard event listener that changes the speedX and speedY using the WASD keys:
'W' increase SpeedY
'S' decrease SpeedY
'A' decrease SpeedX
'D' increase SpeedX
Make sure the SpeedX and Speed Y never go too fast
In the draw function, change the x and y position using the speedX and speedY variables.
If the circle is outside the canvas then move it to the other side. For example, if the ball is over the right side of the canvas, then move it to the other side. Make sure the ball is all of the way off the edge before moving it, otherwise it may pop.
Here is the code I have thus far:
var canvas;
var ctx;
var width = 320;
var height = 240;
var speedX = 0; //how fast the ball is moving in the horizontal direction
var speedY = 0; //how fast the ball is moving in the vertical direction
var radius = 10;
var x = width / 2 - radius; //starting horizontal position
var y = height / 2 - radius; //starting vertical position
var circleColor = "#FF0000";
var rectangleColorBg = "#FFFFFF";
var rectangleColorStroke = "#000000";
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
return setInterval(draw, 10);
}
function circle(x,y,r, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.fill();
}
function rect(x,y,w,h) {
ctx.fillStyle = rectangleColorBg;
ctx.strokeStyle = rectangleColorStroke;
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
function draw() {
//update based on speedX and speed y
//update if the ball position is not inside the canvase.
//draw the background - see function above
rect(0,0,width,height);
//
//draw the circle - see function above
circle(x, y, radius, circleColor);
}
init();
It would be helpful to see what you have tried so far, but here is some example code for the event listener part:
document.addEventListener('keydown', function(event) {
if(event.keyCode == 37) {
// Move ('left');
}
else if(event.keyCode == 39) {
// Move ('right');
}
else if(event.keyCode == 38) {
// Move ('up');
}
else if(event.keyCode == 40) {
// Move ('down');
}
}
How far have you gotten with event listeners? you can also do 'keyup' and such.