Simple Button in HTML5 canvas - javascript

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

Related

Spots in semi-transparent line(canvas drawing)- js

I am trying to make a highlighter,
The problem is with the transparency, maybe due to the lineCap=round, and some other reason there are many dark spots in just one line currently(it should not be). I can't explain more in words,compare the images below
Current look(when I draw 1 line):
The look I want when I draw 1 line:
The look I want when I draw 2 lines:
Gif
Current code:
lineThickess = 50;
lineColor = 'rgba(255,255,0,0.1)';
// wait for the content of the window element
// to load, then performs the operations.
// This is considered best practice.
window.addEventListener('load', () => {
resize(); // Resizes the canvas once the window loads
document.addEventListener('mousedown', startPainting);
document.addEventListener('mouseup', stopPainting);
document.addEventListener('mousemove', sketch);
window.addEventListener('resize', resize);
});
const canvas = document.querySelector('#canvas');
// Context for the canvas for 2 dimensional operations
const ctx = canvas.getContext('2d');
// Resizes the canvas to the available size of the window.
function resize() {
ctx.canvas.width = canvas.offsetWidth;
ctx.canvas.height = canvas.offsetHeight;
}
// Stores the initial position of the cursor
let coord = {
x: 0,
y: 0
};
// This is the flag that we are going to use to
// trigger drawing
let paint = false;
// Updates the coordianates of the cursor when
// an event e is triggered to the coordinates where
// the said event is triggered.
function getPosition(event) {
coord.x = event.clientX - canvas.offsetLeft;
coord.y = event.clientY - canvas.offsetTop;
}
// The following functions toggle the flag to start
// and stop drawing
function startPainting(event) {
paint = true;
getPosition(event);
}
function stopPainting() {
paint = false;
}
function sketch(event) {
if (!paint) return;
ctx.beginPath();
ctx.lineWidth = lineThickess;
// Sets the end of the lines drawn
// to a round shape.
ctx.lineCap = 'round';
ctx.strokeStyle = lineColor;
// The cursor to start drawing
// moves to this coordinate
ctx.moveTo(coord.x, coord.y);
// The position of the cursor
// gets updated as we move the
// mouse around.
getPosition(event);
// A line is traced from start
// coordinate to this coordinate
ctx.lineTo(coord.x, coord.y);
// Draws the line.
ctx.stroke();
}
html,
body {
height: 100%;
}
canvas {
width: 100%;
height: 100%;
border: 1px solid;
}
<canvas id="canvas"></canvas>
The clue
Stack overflow recommended me reviewing other posts related, and I found a clue,and code
But
As shown (and said in the answer), two different paths work well, but loops on same paths don't work as needed
The issue you are seeing is because the intersection of two lines with transparency the result is not the same transparent color, just like in the real world if you hold two sunglasses on top of each other the resulting color is not the same as the individual ones.
What you should do is use Path2D to get the same transparency over the entire object, it could be lines, arches rectangles or any other items, if they are under a common Path2D they all get the same color even on intersections.
Below is a very simple example based on your code
This is how it looks like when the lines cross:
let path = new Path2D()
document.addEventListener('mousemove', sketch)
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
ctx.lineWidth = 10
ctx.lineCap = 'round'
ctx.strokeStyle = 'rgba(255,0,255,0.3)'
function sketch(event) {
let x = event.clientX - canvas.offsetLeft
let y = event.clientY - canvas.offsetTop
path.lineTo(x, y)
}
function drawCircles() {
for (let i = 25; i<=100; i+=25)
ctx.arc(i*2, i, 5, 0, 8)
ctx.fill()
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
drawCircles()
ctx.stroke(path)
requestAnimationFrame(draw)
}
draw()
<canvas id="canvas" width=300 height=150></canvas>
Another example, a bit more complex this time, using an array of Path2D objects, and added a button to change colors, the creation of the color add a new item to the array:
let paths = []
paths.unshift({path: new Path2D(), color:'rgba(255,0,255,0.3)'})
document.addEventListener('mousemove', sketch)
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
ctx.lineWidth = 10
ctx.lineCap = 'round'
function changeColor() {
let newColor = 'rgba(0,255,255,0.3)'
if (paths[0].color == newColor)
newColor = 'rgba(255,0,255,0.3)'
paths.unshift({path:new Path2D(), color: newColor})
}
function sketch(event) {
let x = event.clientX - canvas.offsetLeft
let y = event.clientY - canvas.offsetTop
paths[0].path.lineTo(x, y)
}
function drawCircles() {
for (let i = 25; i<=100; i+=25)
ctx.arc(i*2, i, 5, 0, 8)
ctx.fill()
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
drawCircles()
paths.forEach(e => {
ctx.strokeStyle = e.color
ctx.stroke(e.path)
})
requestAnimationFrame(draw)
}
draw()
<canvas id="canvas" width=250 height=150></canvas>
<button onclick="changeColor()">Change Color</button>
This is how it looks like after changing colors a few times:
Now you have all the tools you need...
It's up to you to decide what is the end of a path and the start of a new one.

Creating a very large scrollable (by holding a right mouse button down) Canvas with Javascript

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';
}
}
}

Resize and move rectangles drawn on canvas

I am allowing the user to draw rectangles on an image. At the same time , the user should be able to resize or move any of the rectangles at any point of time.
With some help, i have been able to draw the rectangles but i am unable to come up with resizing and moving part of it.
The rectangles that are being drawn do not overlap one another and the same has to be validated while resizing and moving too.
I am using javascript and jquery.
This is a demo of what i have done so far :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var startX;
var startY;
var isDown = false;
ctx.strokeStyle = "lightgray";
ctx.lineWidth = 3;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousedown stuff here
startX = mouseX;
startY = mouseY;
isDown = true;
}
function handleMouseUp(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
$("#uplog").html("Up: " + mouseX + " / " + mouseY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseMove(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
if (!isDown) {
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRectangle(mouseX, mouseY);
}
function drawRectangle(mouseX, mouseY) {
var width = mouseX - startX;
var height = mouseY - startY;
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.stroke();
}
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
as i am running short of time and i am not able to figure out how this can be done.
AFAK, HTML canvas element is simply an array of pixels.
Then drawing/moving/resizing rectangles is, simply again, to keep redrawing canvas.
So first, drawn objects need to be stored (maybe in array).
Second, corresponding mouse events are necessary.
Last, canvas redrawing is required.
Like:
var boxes = [];
var tmpBox = null;
document.getElementById("canvas").onmousedown = function(e) {...};
document.getElementById("canvas").onmouseup = function(e) {...};
document.getElementById("canvas").onmouseout = function(e) {...};
document.getElementById("canvas").onmousemove = function(e) {...};
Here is JSFiddle for demo: https://jsfiddle.net/wiany11/p7hxjmsj/14/
These 2 tutorials explain what you want:
http://simonsarris.com/blog/510-making-html5-canvas-useful
http://simonsarris.com/blog/225-canvas-selecting-resizing-shape
In short you should store the borders of the rectangles yourself and detect when the user clicks in the rectangle or on the border.
First you create an array to store your rectangles in
var rectangles = [];
Then you make a method to call every time you want to draw all your rectangles
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < rectangles.length; i++) {
var rect = rectangles[i];
ctx.beginPath();
ctx.rect(rect.startX, rect.startY, rect.endX, rect.endY);
ctx.stroke();
ctx.closePath();
}
}
In your mouseUp you then push the rectangles you have created to the array
function handleMouseUp() {
...
// store the rectangle as an object in your array
var rectangle = {startX: startX, endX: mouseX, startY: startY, endY: mouseY};
rectangles.push(rectangle);
drawRectangles();
}
In your other handlers you can then detect if you click in a rectangle of when your mouse will move in one
You can't just draw objects onto the canvas if you want to move them. You need to create instances of your shape objects and manage those (hit-testing and rendering as required). It is not very complex, but requires a lot more code than you have so far.
Try this tutorial: http://simonsarris.com/blog/510-making-html5-canvas-useful

Return squares to original state

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])

Drawing a circle on the canvas using mouse events

I am trying to draw a circle using mouse on the canvas using mouse events, but it does not draw anything:
tools.circle = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
ctx.moveTo(tool.x0,tool.y0);
};
this.mousemove = function (ev) {
var centerX = Math.max(tool.x0,ev._x) - Math.abs(tool.x0 - ev._x)/2;
var centerY = Math.max(tool.y0,ev._y) - Math.abs(tool.y0 - ev._y)/2;
var distance = Math.sqrt(Math.pow(tool.x0 - ev._x,2) + Math.pow(tool.y0 - ev._y));
context.circle(tool.x0, tool.y0, distance/2,0,Math.PI*2 ,true);
context.stroke();
};
};
What am I doing wrong?
Well, this code snippet doesn't tell us much, but there are a couple of obvious errors in your code.
First, DOM Event object doesn't have _x and _y properties. but rather clientX and clientY or pageX and pageY.
To get relative mouse coordinates from the current event object, you would do something like:
element.onclick = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
}
Next, canvas' 2d context doesn't have a method called circle, but you could write your own, maybe something like:
var ctx = canvas.context;
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI*2, false);
this.fill();
}
Anyhow, here's a test html page to test this out: http://jsfiddle.net/ArtBIT/kneDX/
I hope this helps.
Cheers

Categories