HTML Canvas & JavaScript - Selection Menu - Setting Initial and Sustaining Current Selection - javascript

In the HTML canvas below I have a selection menu which triggers the drawing of an image beside it dependent on what is selected (numbers 1-5 in the example below). The JavaScript uses the pseudo-object approach to storing/manipulating images drawn on the canvas. Besides the EventListener's attached to the canvas, there is one EventListener attached to the whole window which resizes the canvas within a strict aspect ratio when the window size is changed.
The problem I am currently having with this is that the selection is cleared when the EventListener is triggered (when the window size is changed). To replicate this in the example below, you will have to run the code snippet in full screen mode and change your browser window's size. Instead, I would like the the current selection to be maintained after the window (and correspondingly, the canvas') size change. I have tried assigning the current selection to a variable, but I could only get it to leave a static selection where the onHover animation does not work.
Also, related to this, I am trying to set an initial selection that is selected on the first canvas draw until one of the other options is selected. In this case, when the script initially loads, I would like the number 1 and its corresponding image to be automatically selected/displayed until a new selection is made. Again, assigning this as an initialSelection variable or calling makeCurvedRect independently leaves a static selection, by which I mean the curvedRect (image) is not animated onHover.
I'm very unsure how to achieve either of these results so any help will be much appreciated. Apologies for the large amount of code but I could not manage to condense it any more than this.
var c=document.getElementById('game'),
rect = c.getBoundingClientRect(),
ctx=c.getContext('2d');
c.width = window.innerWidth;
c.height = (2/3)*c.width;
numberImages = ['https://i.stack.imgur.com/TZIUz.png','https://i.stack.imgur.com/6beTF.png','https://i.stack.imgur.com/wZk2H.png','https://i.stack.imgur.com/1K743.png','https://i.stack.imgur.com/jMMmQ.png'];
var curvedRect = function(number, x, y, w, h) {
this.text = number.toString();
this.img = new Image();
this.img.src=numberImages[number-1];
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.hovered = false;
this.clicked = false;
this.visible = false;
}
var selected;
curvedRect.prototype.makeCurvedRect = function() {
var delta=0, theta=0;
if (this.hovered) {
delta = (c.height*(3/500));
theta = -0.01;
shadowColor = '#000000';
shadowBlur = 20;
shadowOffsetX = 5;
shadowOffsetY = 5;
} else {
delta = 0;
theta = 0;
shadowColor = '#9F3A9B';
shadowBlur = 0;
shadowOffsetX = 0;
shadowOffsetY = 0;
}
var x = this.x-delta;
var y = this.y-delta;
var w = this.w+(2*delta);
var h = this.h+(2*delta);
var cornerRounder = (c.height*(10/500))
ctx.rotate(theta);
ctx.beginPath();
ctx.lineWidth='12';
ctx.strokeStyle='white';
ctx.moveTo(x+cornerRounder, y);
ctx.lineTo(x+w-cornerRounder, y);
ctx.quadraticCurveTo(x+w, y, x+w, y+cornerRounder);
ctx.lineTo(x+w, y+h-cornerRounder);
ctx.quadraticCurveTo(x+w, y+h, x+w-cornerRounder, y+h);
ctx.lineTo(x+cornerRounder, y+h);
ctx.quadraticCurveTo(x, y+h, x, y+h-cornerRounder);
ctx.lineTo(x, y+cornerRounder);
ctx.quadraticCurveTo(x, y, x+cornerRounder, y);
ctx.shadowColor = shadowColor;
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
ctx.stroke();
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage(this.img, x+(c.width*(2.5/750)), y+(c.height*(2.5/500)), w-cornerRounder/2, h-cornerRounder/2);
ctx.rotate(-theta);
}
curvedRect.prototype.hitTest = function(x, y) {
return (x >= this.x) && (x <= (this.w+this.x)) && (y >= this.y) && (y <= (this.h+this.y));
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
this.hovered = false;
this.clicked = false;
this.lastClicked = false;
this.visible = true;
}
function makeTextForSelected(text, y) {
ctx.font='bold '+(c.height*(12/500))+'px Noto Sans'; // check
ctx.fillStyle='white';
ctx.textAlign='center';
ctx.fillText(text, (c.width*(200/750)), y);
}
selectionForMenu.prototype.makeSelection = function() {
ctx.globalAlpha=0.75;
var fillColor='#A84FA5';
if (this.hovered) {
if (this.clicked) {
if (this.lastClicked) {
fillColor='#E4C7E2';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#D5A9D3';
}
} else if (this.lastClicked) {
fillColor='#D3A4D0';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#BA74B7';
}
} else if (this.lastClicked) {
fillColor='#C78DC5';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#A84FA5';
}
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.fillRect(c.width*(400/750), this.y, c.width*(350/750), c.height*(100/500))
ctx.stroke();
ctx.font=c.height*(10/500)+'px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, c.width*(410/750), this.y+(c.height*(38/500)));
ctx.globalAlpha=1;
}
selectionForMenu.prototype.hitTest = function(x, y) {
return (x >= (c.width*(400/750)) && (x <= c.width) && (y >= this.y) &&
(y <= (this.y+(c.height*(100/500))) && !((x >= c.width*(400/750) && (y > c.height*(450/500))))));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeSelection();
}
catch(err) {}
try {
if(this.shapes[i].lastClicked == true) {
this.shapes[i].rect.makeCurvedRect();
}
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='white';
ctx.fillRect(0, 0, c.width, (c.height*(25/500)));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle='#BC77BA';
ctx.fillRect(0, (c.height*(450/500)), c.width, (c.height*(50/500)));
ctx.stroke();
ctx.font='bold '+(c.height*(10/500))+'px Noto Sans';
ctx.fillStyle='#9F3A9B';
ctx.textAlign='center';
ctx.fillText('Test', (c.width*(365/750)), (c.height*(17/500)));
}
Paint.prototype.setHovered = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].hovered = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setClicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setUnclicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
if (shape.constructor.name==this.shapes[i].constructor.name) {
this.shapes[i].clicked = false;
if (shape instanceof selectionForMenu) {
this.shapes[i].lastClicked = this.shapes[i] == shape;
if (this.shapes[i].lastClicked == true) {
this.shapes[i].rect.visible = true;
} else {
this.shapes[i].rect.visible = false;
}
}
}
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].visible == true && this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var numbers = [1,2,3,4,5];
var paint = new Paint(c);
var selection = [];
for (var i=0; i<numbers.length; i++) {
selection.push(new selectionForMenu(i+1, numbers[i], c.height*(25/500)+(c.height*((i*100)/500))));
}
for (var i=0; i<numbers.length; i++) {
var img = new curvedRect(i+1, (c.width*(112.5/750)), (c.height*(100/500)), (c.height*(175/500)), (c.height*(175/500)));
paint.addShape(img)
selection[i].rect = img;
}
for (var i=0; i<numbers.length; i++) {
paint.addShape(selection[i])
}
paint.render();
var clickedShape, clickIndex=0;
function mouseDown(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
if (shape instanceof selectionForMenu) {
if (clickIndex==0) {
clickedShape=shape;
clickIndex=1;
} else if (clickIndex==1) {
clickIndex=0;
}
}
paint.setClicked(shape);
}
function mouseUp(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
if (clickedShape instanceof selectionForMenu) {
if (x>c.width*(400/750) && y>c.height*(25/500) && y<c.height*(450/500)) {
paint.setUnclicked(shape);
} else if (shape && !(shape instanceof selectionForMenu)) {
paint.setUnclicked(shape);
}
}
}
function mouseMove(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
paint.setHovered(shape);
}
function paintCanvas() {
c.width = window.innerWidth;
c.height = (2/3)*c.width;
ctx=c.getContext('2d');
rect = c.getBoundingClientRect();
paint = new Paint(c);
selection = [];
for (var i=0; i<numbers.length; i++) {
selection.push(new selectionForMenu(i+1, numbers[i], c.height*(25/500)+(c.height*((i*100)/500))));
}
for (var i=0; i<numbers.length; i++) {
var img = new curvedRect(i+1, (c.width*(112.5/750)), (c.height*(100/500)), (c.height*(175/500)), (c.height*(175/500)));
paint.addShape(img)
selection[i].rect = img;
}
for (var i=0; i<numbers.length; i++) {
paint.addShape(selection[i])
}
paint.render();
}
paintCanvas();
window.addEventListener('resize', paintCanvas);
c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style>
</head>
<body>
<div id='container'>
<canvas id="game"></canvas>
</div>
<script type='text/javascript' src='scaleStack.js'></script>
</body>
</html>

you must make the paint class (in your code) once,
function Game (elementID,width,height){
this.elementID = elementID;
this.element = document.getElementById(elementID);
this.width = width;
this.height = height;
this.palette = {
color1:'#fff',
color2:'#000',
color3:'#9F3A9B',
color4:'#a84ea5',
color5:'#b56ab2',
color6:'#bf7dbd',
color7:'#d5a8d2'
};
this.element.style.width = width + 'px';
this.element.style.height= height + 'px';
this.element.style.border='solid thin ' + this.palette.color2;
this.element.style.display= 'block';
//this.element.style.margin='1em auto';
this.element.style.background=this.palette.color3;
this.initialGame();
}
Game.prototype.initialGame = function(){
this.canvas = document.createElement("canvas");
this.canvas.width = this.width;
this.canvas.height = this.height;
this.element.appendChild(this.canvas);
this.initialTitle();
this.initialSideButtons();
this.initialBoard();
this.initialFooter();
// initial selection
this.sideButtons.select(this.sideButtons.buttons[0]);
this.resize(this.width,this.height);
this.render();
this.attachEvents();
}
Game.prototype.attachEvents = function(){
var element = this.element;
var getX = function(evt){return evt.offsetX || evt.layerX || (evt.clientX - element.offsetLeft);};
var getY = function(evt){return evt.offsetY || evt.layerY || (evt.clientY - element.offsetTop);};
var game = this;
this.element.addEventListener('mousemove',function(evt){
game.hover(getX(evt),getY(evt));
game.render();
});
this.element.addEventListener('click',function(evt){
game.sideButtons.click();
game.render();
});
}
Game.prototype.onSelect = function(button){
this.selected = button;
};
Game.prototype.hover=function(x,y){
this.hoverX = x;
this.hoverY = y;
};
Game.prototype.initialBoard = function(){
var game = this;
var Board = function(){
this.left = 0;
this.top = 0;
this.width =0;
this.height=0;
};
Board.prototype.render = function(ctx){
if(game.selected){
var shapeWidth = this.width/3;
ctx.fillStyle = game.palette.color1;
ctx.strokeStyle = game.palette.color1;
var fontSize = 14;
ctx.font = 'bold '+ fontSize +'px Noto Sans';
ctx.textAlign='center';
ctx.lineWidth=8;
ctx.lineJoin = 'round';
ctx.strokeRect(this.left + this.width/2 - (shapeWidth/2),this.height/2-(shapeWidth/2) + this.top,shapeWidth,shapeWidth);
ctx.fillText(game.selected.text,this.left + this.width/2,this.height/2 + this.top );
}
};
this.board = new Board();
};
Game.prototype.initialSideButtons = function(){
var game = this;
var ButtonBar =function(text){
this.text = text;
this.left = 0;
this.top = 0;
this.width = 1;
this.height= 1;
this.selected=false;
};
ButtonBar.prototype.hitTest=function(x,y){
return (this.left < x) && (x < (this.left + this.width)) &&
(this.top <y) && (y < (this.top + this.height));
};
ButtonBar.prototype.getColor=function(){
var hovered = this.hitTest(game.hoverX,game.hoverY);
if(this.selected){
if(hovered)
{
return game.palette.color7;
}
return game.palette.color6;
}
if(hovered){
return game.palette.color5;
}
return game.palette.color4;
};
ButtonBar.prototype.render = function(ctx){
var fontSize = 14;
ctx.fillStyle = this.getColor();
ctx.fillRect(this.left,this.top,this.width,this.height);
ctx.fillStyle = game.palette.color1;
ctx.textAlign = 'left';
ctx.font ='bold '+ fontSize +'px Noto Sans';
ctx.fillText(this.text,this.left + 10,this.top+ this.height/2);
};
var SideButtons = function(){
this.buttons = [];
this.width = 1;
this.height= 1;
this.left=1;
this.top=1;
};
SideButtons.prototype.render = function(ctx){
if(!this.buttons.length){
return;
}
var height = this.height / this.buttons.length ;
for(var i=0;i<this.buttons.length;i++){
var btn = this.buttons[i];
btn.left = this.left;
btn.top = i * height + this.top;
btn.width = this.width;
btn.height = height;
this.buttons[i].render(ctx);
}
};
SideButtons.prototype.click = function(){
var current = null;
for(var i=0;i<this.buttons.length;i++){
var btn = this.buttons[i];
if( btn.hitTest(game.hoverX,game.hoverY))
{
this.select(btn);
break;
}
}
};
SideButtons.prototype.select = function(btn)
{
for(var i=0;i<this.buttons.length;i++)
{
this.buttons[i].selected = false;
}
btn.selected=true;
game.onSelect(btn);
};
this.sideButtons = new SideButtons();
var btn1 = new ButtonBar('Button 1');
var btn2 = new ButtonBar('Button 2');
var btn3 = new ButtonBar('Button 3');
var btn4 = new ButtonBar('Button 4');
this.sideButtons.buttons.push(btn1);
this.sideButtons.buttons.push(btn2);
this.sideButtons.buttons.push(btn3);
this.sideButtons.buttons.push(btn4);
};
Game.prototype.initialTitle = function(){
var Title = function(value,width,height){
this.value=value;
this.width = width;
this.height= height;
};
var game = this;
Title.prototype.render=function(ctx){
var k = 2;
var fontSize = this.height / k;
ctx.fillStyle=game.palette.color1;
ctx.fillRect(0,0,this.width,this.height);
ctx.font='bold '+ fontSize +'px Noto Sans'; // check
ctx.fillStyle=game.palette.color3;
ctx.textAlign='center';
ctx.fillText(this.value,this.width/2,this.height - fontSize/2);
};
this.title = new Title('Test',this.width,this.height / 10);
}
Game.prototype.initialFooter = function(){
var Footer = function(){
this.width = 1;
this.height= 1;
this.left=0;
this.top=0;
}
var game = this;
Footer.prototype.render = function(ctx){
ctx.fillStyle = game.palette.color5;
ctx.fillRect(this.left,this.top,this.width,this.height);
};
this.footer = new Footer();
};
Game.prototype.resetCanvas = function(){
this.canvas.width = this.width;
this.canvas.height = this.height;
};
Game.prototype.render = function(){
this.resetCanvas();
var context = this.canvas.getContext('2d');
this.title.render(context);
this.sideButtons.render(context);
this.board.render(context);
this.footer.render(context);
};
Game.prototype.resize = function (width,height){
this.width = width;
this.height= height;
this.element.style.width = width + 'px';
this.element.style.height= height+ 'px';
this.title.height = this.height / 14;
this.title.width = this.width;
this.footer.height = this.title.height;
this.footer.width = this.width;
this.footer.top = this.height - this.footer.height;
this.footer.left = 0;
this.board.top = this.title.height;
this.board.left = 0;
this.board.width = this.width - 250;//or -> this.width / 2
this.board.height= this.height - this.title.height - this.footer.height;
this.sideButtons.left= this.board.width;
this.sideButtons.top = this.board.top;
this.sideButtons.width = this.width - this.board.width;
this.sideButtons.height = this.board.height;
this.render();
};
var game = new Game('game',window.innerWidth -50,window.innerWidth * 2/3);
window.addEventListener('resize', function(){
game.resize(window.innerWidth -50,window.innerWidth * 2/3);
});
<div id='container'>
<div id="game"></div>
</div>

The issue is that your resize handler calls paintCanvas and in your paintCanvas method you are assigning your global paint variable to new an entirely new instance of paint. This entirely wipes out your state and forces the canvas to be redrawn to match the initial state of an initial page load. Instead, you need to maintain your state, clear your canvas and render it again with its existing state but just with new sizes.
function paintCanvas() {
c.width = window.innerWidth;
c.height = (2/3)*c.width;
ctx=c.getContext('2d');
rect = c.getBoundingClientRect();
//paint = new Paint(c);
Commenting out //paint = new Paint(c); leaves your state intact. You still some remnants you need to flush out and redraw since you are no longer destroying your state.
var c=document.getElementById('game'),
rect = c.getBoundingClientRect(),
ctx=c.getContext('2d');
c.width = window.innerWidth;
c.height = (2/3)*c.width;
numberImages = ['https://i.stack.imgur.com/TZIUz.png','https://i.stack.imgur.com/6beTF.png','https://i.stack.imgur.com/wZk2H.png','https://i.stack.imgur.com/1K743.png','https://i.stack.imgur.com/jMMmQ.png'];
var curvedRect = function(number, x, y, w, h) {
this.text = number.toString();
this.img = new Image();
this.img.src=numberImages[number-1];
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.hovered = false;
this.clicked = false;
this.visible = false;
}
var selected;
curvedRect.prototype.makeCurvedRect = function() {
var delta=0, theta=0;
if (this.hovered) {
delta = (c.height*(3/500));
theta = -0.01;
shadowColor = '#000000';
shadowBlur = 20;
shadowOffsetX = 5;
shadowOffsetY = 5;
} else {
delta = 0;
theta = 0;
shadowColor = '#9F3A9B';
shadowBlur = 0;
shadowOffsetX = 0;
shadowOffsetY = 0;
}
var x = this.x-delta;
var y = this.y-delta;
var w = this.w+(2*delta);
var h = this.h+(2*delta);
var cornerRounder = (c.height*(10/500))
ctx.rotate(theta);
ctx.beginPath();
ctx.lineWidth='12';
ctx.strokeStyle='white';
ctx.moveTo(x+cornerRounder, y);
ctx.lineTo(x+w-cornerRounder, y);
ctx.quadraticCurveTo(x+w, y, x+w, y+cornerRounder);
ctx.lineTo(x+w, y+h-cornerRounder);
ctx.quadraticCurveTo(x+w, y+h, x+w-cornerRounder, y+h);
ctx.lineTo(x+cornerRounder, y+h);
ctx.quadraticCurveTo(x, y+h, x, y+h-cornerRounder);
ctx.lineTo(x, y+cornerRounder);
ctx.quadraticCurveTo(x, y, x+cornerRounder, y);
ctx.shadowColor = shadowColor;
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
ctx.stroke();
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage(this.img, x+(c.width*(2.5/750)), y+(c.height*(2.5/500)), w-cornerRounder/2, h-cornerRounder/2);
ctx.rotate(-theta);
}
curvedRect.prototype.hitTest = function(x, y) {
return (x >= this.x) && (x <= (this.w+this.x)) && (y >= this.y) && (y <= (this.h+this.y));
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
this.hovered = false;
this.clicked = false;
this.lastClicked = false;
this.visible = true;
}
function makeTextForSelected(text, y) {
ctx.font='bold '+(c.height*(12/500))+'px Noto Sans'; // check
ctx.fillStyle='white';
ctx.textAlign='center';
ctx.fillText(text, (c.width*(200/750)), y);
}
selectionForMenu.prototype.makeSelection = function() {
ctx.globalAlpha=0.75;
var fillColor='#A84FA5';
if (this.hovered) {
if (this.clicked) {
if (this.lastClicked) {
fillColor='#E4C7E2';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#D5A9D3';
}
} else if (this.lastClicked) {
fillColor='#D3A4D0';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#BA74B7';
}
} else if (this.lastClicked) {
fillColor='#C78DC5';
makeTextForSelected(this.text, c.height*(375/500));
} else {
fillColor='#A84FA5';
}
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.fillRect(c.width*(400/750), this.y, c.width*(350/750), c.height*(100/500))
ctx.stroke();
ctx.font=c.height*(10/500)+'px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, c.width*(410/750), this.y+(c.height*(38/500)));
ctx.globalAlpha=1;
}
selectionForMenu.prototype.hitTest = function(x, y) {
return (x >= (c.width*(400/750)) && (x <= c.width) && (y >= this.y) &&
(y <= (this.y+(c.height*(100/500))) && !((x >= c.width*(400/750) && (y > c.height*(450/500))))));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeSelection();
}
catch(err) {}
try {
if(this.shapes[i].lastClicked == true) {
this.shapes[i].rect.makeCurvedRect();
}
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='white';
ctx.fillRect(0, 0, c.width, (c.height*(25/500)));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle='#BC77BA';
ctx.fillRect(0, (c.height*(450/500)), c.width, (c.height*(50/500)));
ctx.stroke();
ctx.font='bold '+(c.height*(10/500))+'px Noto Sans';
ctx.fillStyle='#9F3A9B';
ctx.textAlign='center';
ctx.fillText('Test', (c.width*(365/750)), (c.height*(17/500)));
}
Paint.prototype.setHovered = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].hovered = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setClicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setUnclicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
if (shape.constructor.name==this.shapes[i].constructor.name) {
this.shapes[i].clicked = false;
if (shape instanceof selectionForMenu) {
this.shapes[i].lastClicked = this.shapes[i] == shape;
if (this.shapes[i].lastClicked == true) {
this.shapes[i].rect.visible = true;
} else {
this.shapes[i].rect.visible = false;
}
}
}
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].visible == true && this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var numbers = [1,2,3,4,5];
var paint = new Paint(c);
var selection = [];
for (var i=0; i<numbers.length; i++) {
selection.push(new selectionForMenu(i+1, numbers[i], c.height*(25/500)+(c.height*((i*100)/500))));
}
for (var i=0; i<numbers.length; i++) {
var img = new curvedRect(i+1, (c.width*(112.5/750)), (c.height*(100/500)), (c.height*(175/500)), (c.height*(175/500)));
paint.addShape(img)
selection[i].rect = img;
}
for (var i=0; i<numbers.length; i++) {
paint.addShape(selection[i])
}
paint.render();
var clickedShape, clickIndex=0;
function mouseDown(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
if (shape instanceof selectionForMenu) {
if (clickIndex==0) {
clickedShape=shape;
clickIndex=1;
} else if (clickIndex==1) {
clickIndex=0;
}
}
paint.setClicked(shape);
}
function mouseUp(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
if (clickedShape instanceof selectionForMenu) {
if (x>c.width*(400/750) && y>c.height*(25/500) && y<c.height*(450/500)) {
paint.setUnclicked(shape);
} else if (shape && !(shape instanceof selectionForMenu)) {
paint.setUnclicked(shape);
}
}
}
function mouseMove(event) {
var x = (event.pageX-rect.left)/(rect.right-rect.left)*c.width;
var y = (event.pageY-rect.top)/(rect.bottom-rect.top)*c.height;
var shape = paint.select(x, y);
paint.setHovered(shape);
}
function paintCanvas() {
c.width = window.innerWidth;
c.height = (2/3)*c.width;
ctx=c.getContext('2d');
rect = c.getBoundingClientRect();
//paint = new Paint(c);
selection = [];
for (var i=0; i<numbers.length; i++) {
selection.push(new selectionForMenu(i+1, numbers[i], c.height*(25/500)+(c.height*((i*100)/500))));
}
for (var i=0; i<numbers.length; i++) {
var img = new curvedRect(i+1, (c.width*(112.5/750)), (c.height*(100/500)), (c.height*(175/500)), (c.height*(175/500)));
paint.addShape(img)
selection[i].rect = img;
}
for (var i=0; i<numbers.length; i++) {
paint.addShape(selection[i])
}
paint.render();
}
paintCanvas();
window.addEventListener('resize', paintCanvas);
c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style>
</head>
<body>
<div id='container'>
<canvas id="game"></canvas>
</div>
<script type='text/javascript' src='scaleStack.js'></script>
</body>
</html>

Related

How to force lines to render behind other things in HTML5 Canvas

I have been trying to make a nodal graph visualizer, but I am having trouble getting the lines to display behind the nodal points. I have already tried to use context.globalCompositeOperation, but to no avail. I have tried to implement rendering it in the order I wanted it to be drawn. Here is the full code for the project:
var canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var c = canvas.getContext('2d');
var nodes = [];
var has_clicked = false;
var user_has_picked_up_object = false;
var picked_up_id;
class Vector2 {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
document.onmousemove = function(e){
mouse_pos.x = e.pageX;
mouse_pos.y = e.pageY;
}
document.onmousedown = function(e) {
has_clicked = true;
}
document.addEventListener('mouseup', function(e) {
has_clicked = false;
user_has_picked_up_object = false;
})
var mouse_pos = new Vector2(0, 0);
class Connection {
constructor(node1, node2) {
this.node1 = node1;
this.node2 = node2;
this.isDrawn = false;
}
}
function DistSQ(p1, p2) {
return Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2);
}
class Node {
constructor(pos, id) {
this.pos = pos;
this.radius = 10;
this.radius_squared = 100;
this.connections = [];
this.id = id;
}
AddConnection(conn) {
this.connections.push(conn);
}
RemoveConnection(conn) {
return this.connections.pop(conn);
}
UpdatePosition() {
if(DistSQ(this.pos, mouse_pos) < this.radius_squared && has_clicked) {
if(user_has_picked_up_object && picked_up_id == this.id) {
this.pos = mouse_pos;
}
else {
user_has_picked_up_object = true;
picked_up_id = this.id;
}
}
else {
this.pos = new Vector2(this.pos.x, this.pos.y);
}
}
}
function DrawLines(conns) {
c.beginPath();
c.lineWidth = 1;
c.strokeStyle = 'black';
conns.forEach(element => {
c.moveTo(element.node1.pos.x, element.node1.pos.y);
c.lineTo(element.node2.pos.x, element.node2.pos.y);
});
c.stroke();
}
function DrawCircles(nds) {
c.beginPath();
c.lineWidth = 1;
nds.forEach(element => {
c.strokeStyle = 'black';
c.moveTo(element.pos.x+element.radius, element.pos.y);
c.arc(element.pos.x, element.pos.y, element.radius, 0, 2 * Math.PI, false);
});
c.stroke();
}
function Update() {
requestAnimationFrame(Update);
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
for(var i = 0; i < nodes.length; i++) {
nodes[i].UpdatePosition();
DrawLines(nodes[i].connections);
}
DrawCircles(nodes);
}
function Initialize() {
for(var y = 0, i = 0; y < 5; y++) {
for(var x = 0; x < 5; x++, i++) {
nodes.push(new Node(new Vector2(x*20+20, y*20+20), i));
}
}
for(var i = 1; i < nodes.length; i++) {
nodes[i].AddConnection(new Connection(nodes[i], nodes[i-1]));
}
Update();
}
Initialize();
Thanks to #enxaneta, for giving me the solution to this problem, so I copied his answer here:
The lines are behind the circles- In your function DrawCircles add c.fillStyle = "white";c.fill() – enxaneta

What is the right offset for better drag effect?

I am trying to make my cloud draggable. It works but as can be seen in the example that i have, the cloud always clippes from the center to the mouse possition.
Here is my current set-up.
$(document).ready(function () {
var canvas = document.getElementById("background-canvas");
canvas.width = $(window).width();
canvas.height = $(window).height();
canvas.style.zIndex = -1;
var ctx = canvas.getContext("2d");
var mousePosition = new Vector2d(0,0);
var background = new Background(ctx, canvas.width, canvas.height, new Color(224,247,250,0.8));
var cloud = new Cloud(background, 300, 100, new Vector2d(10,10), 20, 1000);
img=new Image();
img.src="https://i.imgur.com/hIVsoho.png";
background.addCloud(cloud);
for (var i = 0; i < background.allClouds.length; i++){
var selectedCloud = background.allClouds[i];
for (var j = 0; j < selectedCloud.maxNumberofPixels; j++){
var pixel = cloud.createPixel(); // TODO: cloud shall not define pixel.
//new Pixel(2, 4, getRandomLocationWithinParent(selectedCloud), new Vector2d(0,5), new Vector2d(0,0), new Color(0,0,128,1));
cloud.addPixel(pixel);
}
}
/*
* Input listeners
*/
document.addEventListener("mousemove", function (evt) {
mousePosition = getMousePos(canvas, evt);
}, false);
document.addEventListener("mousedown", function (evt){
console.log(mousePosition);
for(var i = 0; i < background.allClouds.length; i++)
if(background.allClouds[i].hover(mousePosition))
background.allClouds[i].isClicked = true;
}, false)
document.addEventListener("mouseup", function (evt){
for(var i = 0; i < background.allClouds.length; i++)
if(background.allClouds[i].hover(mousePosition))
background.allClouds[i].isClicked = false;
}, false)
setInterval(updateBackground, 20);
function updateBackground() {
// paint background color.
ctx.fillStyle = background.color.getColorString();
ctx.fillRect(0,0,background.width, background.height);
// paint clouds
for(var i = 0; i < background.allClouds.length; i++){
var selectedCloud = background.allClouds[i];
//ctx.fillStyle = selectedCloud.color.getColorString();
//ctx.fillRect(0, 0, selectedCloud.width, selectedCloud.height); rectangle view of cloud.
// paint rain
var deadPixelContainer = [];
for (var j = 0; j < selectedCloud.allPixels.length; j++){
var selectedPixel = selectedCloud.allPixels[j];
ctx.fillStyle = selectedPixel.color.getColorString();
ctx.save();
ctx.translate(selectedPixel.location.x, selectedPixel.location.y);
ctx.fillRect(-selectedPixel.width / 2, -selectedPixel.height / 2, selectedPixel.width, selectedPixel.height);
ctx.restore();
if(!selectedPixel.alive){
deadPixelContainer.push(selectedPixel);
continue;
}
selectedPixel.update();
selectedPixel.checkEdges(background);
}
if(deadPixelContainer.length > 0){
selectedCloud.removePixels(deadPixelContainer);
}
ctx.save();
ctx.translate(selectedCloud.location.x, selectedCloud.location.y);
ctx.drawImage(img,0,0,img.width,img.height,-25, -10,350,100);
ctx.restore();
cloud.update(mousePosition);
}
}
// TODO: Create object for mouse
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return new Vector2d(evt.clientX - rect.left, evt.clientY - rect.top);
}
});
function Cloud(background, width, height, location, startNumberOfPixels, maxNumberofPixels){
this.width = width;
this.height = height;
this.location = location;
this.allPixels = [];
this.maxNumberofPixels = maxNumberofPixels;
this.color = new Color(255,255,255,0.5);
this.isClicked = false;
this.rainStrength = 5; // how often cloud spawns new pixels per update cycle.
this.addPixel = function(pixel){
if(this.allPixels.length <= startNumberOfPixels)
this.allPixels.push(pixel);
}
this.update = function(mousePosition){
// make cloud draggable
if(this.isClicked){
var offsetX = mousePosition.x - this.location.x;
var offsetY = mousePosition.y - this.location.y;
this.location = new Vector2d(this.location.x + offsetX - this.width/2, this.location.y + offsetY - this.height/2);
}
// add more pixels overtime.
if(this.allPixels.length <= this.maxNumberofPixels)
for(var i = 0; i < this.rainStrength; i++)
this.allPixels.push(this.createPixel());
}
this.hover = function(mousePosition){
if(mousePosition.x > this.location.x
&& mousePosition.x < this.location.x + this.width
&& mousePosition.y > this.location.y
&& mousePosition.y < this.location.y + this.height)
return true;
return false;
}
this.createPixel = function(){
return new Pixel(2, 4, this.getRandomLocation(), new Vector2d(0,7), new Vector2d(0,0.05), new Color(0,0,128,1));
}
this.removePixels = function(deadPixelContainer){
for(var i = 0; i < deadPixelContainer.length; i++){
try{
var pixelContainer = this.allPixels.slice();
pixelContainer.splice(this.allPixels.findIndex(v => v === deadPixelContainer[i]), 1).slice();
this.allPixels = pixelContainer.slice();
}catch(e){
console.log(e);
}
}
}
this.getRandomLocation = function(){
var minWidth = this.location.x;
var maxWidth = this.location.x + this.width;
var minHeight = this.location.y + this.height/2; // don't count upper part of cloud. Rain forms at the bottom.
var maxHeight = this.location.y + this.height;
var randomWidthLocation = Math.random() * (maxWidth - minWidth + 1)+minWidth;
var randomHeightLocation = Math.random() * (maxHeight - minHeight + 1) + minHeight;
return new Vector2d(randomWidthLocation, randomHeightLocation);
}
}
function Background(ctx, width, height, color){
this.width = width;
this.height = height;
this.color = color; //"#191919"
this.isPaused = false;
this.allPixels = []; // might need to be removed.
this.allClouds = [];
this.pixelCount = 150;
this.addCloud = function(cloud){
this.allClouds.push(cloud);
};
this.refreshCanvas = function(){
this.width = $(window).width();
this.height = $(window).height();
};
this.addPixelOn = function(pixelWidht, pixelHeight, location, velocity, acceleration, color) { // might need to be removed.
var pixel = new Pixel(pixelWidht, pixelHeight, location, velocity, acceleration, color);
this.allPixels.push(pixel);
};
this.addPixel = function(pixelWidht, pixelHeight, velocity, acceleration, color) { // might need to be removed.
var location = new Vector2d(Math.random() * this.width, Math.random() * this.height);
this.addPixelOn(pixelWidht, pixelHeight, location, velocity, acceleration, color);
};
}
function Pixel(widht, height, location, velocity, acceleration, color) {
this.height = height;
this.width = widht;
this.color = color; //"#00CC33"
this.location = location;
this.velocity = velocity;
this.acceleration = acceleration;
this.alive = true;
this.update = function () {
this.velocity.add(this.acceleration);
//this.velocity.limit(topspeed);
this.location.add(this.velocity);
};
this.checkEdges = function (background) {
if (this.location.y > background.height) {
this.alive = false;
}
};
this.setColor = function(color){
this.color = color;
};
this.setHeight = function(height){
this.height = height;
};
this.setWidth = function(width){
this.width = width;
}
}
function Color(r,g,b,o){
this.red = r;
this.green = g;
this.blue = b;
this.opacity = o;
this.getColorString = function(){
return "rgba("+this.red+","+this.green+","+this.blue+","+this.opacity+")";
}
}
function Vector2d(x, y) {
this.x = x;
this.y = y;
this.add = function (vector2d) {
this.x += vector2d.x;
this.y += vector2d.y;
};
this.sub = function (vector2d) {
this.x -= vector2d.x;
this.y -= vector2d.y;
};
this.mult = function (mult) {
this.x *= mult;
this.y *= mult;
};
this.div = function (div) {
this.x /= div;
this.y /= div;
};
this.mag = function () {
return Math.sqrt(this.x * this.x, this.y * this.y);
};
this.norm = function () {
var m = this.mag();
if (m !== 0) {
this.div(m);
}
}
}
#background-canvas {
position: fixed;
width: 100%;
height: 100%
background-color:red;
top:0;
left:0;
}
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<canvas id="background-canvas" />
</body>
</html>
The code that sets the location of the cloud when dragged is this:
// make cloud draggable
if(this.isClicked){
var offsetX = mousePosition.x - this.location.x;
var offsetY = mousePosition.y - this.location.y;
this.location = new Vector2d(this.location.x + offsetX - this.width/2, this.location.y + offsetY - this.height/2);
}
This does not work correctly. I want it not to clip to the center.
Can any one help me with this ?
If its not completely clear what the question is or if you need any extra information please ask.
You need to remember the click location within the cloud's local coordinate system:
if(background.allClouds[i].hover(mousePosition)) {
background.allClouds[i].isClicked = true;
background.allClouds[i].clickLocalPosition = new Vector2d(mousePosition.x, mousePosition.y);
background.allClouds[i].clickLocalPosition.sub(background.allClouds[i].location);
}
Then, when you update, you calculate the new position based on the click location:
this.location.x = mousePosition.x - this.clickLocalPosition.x;
this.location.y = mousePosition.y - this.clickLocalPosition.y;
$(document).ready(function () {
var canvas = document.getElementById("background-canvas");
canvas.width = $(window).width();
canvas.height = $(window).height();
canvas.style.zIndex = -1;
var ctx = canvas.getContext("2d");
var mousePosition = new Vector2d(0,0);
var background = new Background(ctx, canvas.width, canvas.height, new Color(224,247,250,0.8));
var cloud = new Cloud(background, 300, 100, new Vector2d(10,10), 20, 1000);
img=new Image();
img.src="https://i.imgur.com/hIVsoho.png";
background.addCloud(cloud);
for (var i = 0; i < background.allClouds.length; i++){
var selectedCloud = background.allClouds[i];
for (var j = 0; j < selectedCloud.maxNumberofPixels; j++){
var pixel = cloud.createPixel(); // TODO: cloud shall not define pixel.
//new Pixel(2, 4, getRandomLocationWithinParent(selectedCloud), new Vector2d(0,5), new Vector2d(0,0), new Color(0,0,128,1));
cloud.addPixel(pixel);
}
}
/*
* Input listeners
*/
document.addEventListener("mousemove", function (evt) {
mousePosition = getMousePos(canvas, evt);
}, false);
document.addEventListener("mousedown", function (evt){
console.log(mousePosition);
for(var i = 0; i < background.allClouds.length; i++)
if(background.allClouds[i].hover(mousePosition)) {
background.allClouds[i].isClicked = true;
background.allClouds[i].clickLocalPosition = new Vector2d(mousePosition.x, mousePosition.y);
background.allClouds[i].clickLocalPosition.sub(background.allClouds[i].location);
}
}, false)
document.addEventListener("mouseup", function (evt){
for(var i = 0; i < background.allClouds.length; i++)
if(background.allClouds[i].hover(mousePosition))
background.allClouds[i].isClicked = false;
}, false)
setInterval(updateBackground, 20);
function updateBackground() {
// paint background color.
ctx.fillStyle = background.color.getColorString();
ctx.fillRect(0,0,background.width, background.height);
// paint clouds
for(var i = 0; i < background.allClouds.length; i++){
var selectedCloud = background.allClouds[i];
//ctx.fillStyle = selectedCloud.color.getColorString();
//ctx.fillRect(0, 0, selectedCloud.width, selectedCloud.height); rectangle view of cloud.
// paint rain
var deadPixelContainer = [];
for (var j = 0; j < selectedCloud.allPixels.length; j++){
var selectedPixel = selectedCloud.allPixels[j];
ctx.fillStyle = selectedPixel.color.getColorString();
ctx.save();
ctx.translate(selectedPixel.location.x, selectedPixel.location.y);
ctx.fillRect(-selectedPixel.width / 2, -selectedPixel.height / 2, selectedPixel.width, selectedPixel.height);
ctx.restore();
if(!selectedPixel.alive){
deadPixelContainer.push(selectedPixel);
continue;
}
selectedPixel.update();
selectedPixel.checkEdges(background);
}
if(deadPixelContainer.length > 0){
selectedCloud.removePixels(deadPixelContainer);
}
ctx.save();
ctx.translate(selectedCloud.location.x, selectedCloud.location.y);
ctx.drawImage(img,0,0,img.width,img.height,-25, -10,350,100);
ctx.restore();
cloud.update(mousePosition);
}
}
// TODO: Create object for mouse
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return new Vector2d(evt.clientX - rect.left, evt.clientY - rect.top);
}
});
function Cloud(background, width, height, location, startNumberOfPixels, maxNumberofPixels){
this.width = width;
this.height = height;
this.location = location;
this.allPixels = [];
this.maxNumberofPixels = maxNumberofPixels;
this.color = new Color(255,255,255,0.5);
this.isClicked = false;
this.rainStrength = 5; // how often cloud spawns new pixels per update cycle.
this.addPixel = function(pixel){
if(this.allPixels.length <= startNumberOfPixels)
this.allPixels.push(pixel);
}
this.update = function(mousePosition){
// make cloud draggable
if(this.isClicked){
this.location.x = mousePosition.x - this.clickLocalPosition.x;
this.location.y = mousePosition.y - this.clickLocalPosition.y;
}
// add more pixels overtime.
if(this.allPixels.length <= this.maxNumberofPixels)
for(var i = 0; i < this.rainStrength; i++)
this.allPixels.push(this.createPixel());
}
this.hover = function(mousePosition){
if(mousePosition.x > this.location.x
&& mousePosition.x < this.location.x + this.width
&& mousePosition.y > this.location.y
&& mousePosition.y < this.location.y + this.height)
return true;
return false;
}
this.createPixel = function(){
return new Pixel(2, 4, this.getRandomLocation(), new Vector2d(0,7), new Vector2d(0,0.05), new Color(0,0,128,1));
}
this.removePixels = function(deadPixelContainer){
for(var i = 0; i < deadPixelContainer.length; i++){
try{
var pixelContainer = this.allPixels.slice();
pixelContainer.splice(this.allPixels.findIndex(v => v === deadPixelContainer[i]), 1).slice();
this.allPixels = pixelContainer.slice();
}catch(e){
console.log(e);
}
}
}
this.getRandomLocation = function(){
var minWidth = this.location.x;
var maxWidth = this.location.x + this.width;
var minHeight = this.location.y + this.height/2; // don't count upper part of cloud. Rain forms at the bottom.
var maxHeight = this.location.y + this.height;
var randomWidthLocation = Math.random() * (maxWidth - minWidth + 1)+minWidth;
var randomHeightLocation = Math.random() * (maxHeight - minHeight + 1) + minHeight;
return new Vector2d(randomWidthLocation, randomHeightLocation);
}
}
function Background(ctx, width, height, color){
this.width = width;
this.height = height;
this.color = color; //"#191919"
this.isPaused = false;
this.allPixels = []; // might need to be removed.
this.allClouds = [];
this.pixelCount = 150;
this.addCloud = function(cloud){
this.allClouds.push(cloud);
};
this.refreshCanvas = function(){
this.width = $(window).width();
this.height = $(window).height();
};
this.addPixelOn = function(pixelWidht, pixelHeight, location, velocity, acceleration, color) { // might need to be removed.
var pixel = new Pixel(pixelWidht, pixelHeight, location, velocity, acceleration, color);
this.allPixels.push(pixel);
};
this.addPixel = function(pixelWidht, pixelHeight, velocity, acceleration, color) { // might need to be removed.
var location = new Vector2d(Math.random() * this.width, Math.random() * this.height);
this.addPixelOn(pixelWidht, pixelHeight, location, velocity, acceleration, color);
};
}
function Pixel(widht, height, location, velocity, acceleration, color) {
this.height = height;
this.width = widht;
this.color = color; //"#00CC33"
this.location = location;
this.velocity = velocity;
this.acceleration = acceleration;
this.alive = true;
this.update = function () {
this.velocity.add(this.acceleration);
//this.velocity.limit(topspeed);
this.location.add(this.velocity);
};
this.checkEdges = function (background) {
if (this.location.y > background.height) {
this.alive = false;
}
};
this.setColor = function(color){
this.color = color;
};
this.setHeight = function(height){
this.height = height;
};
this.setWidth = function(width){
this.width = width;
}
}
function Color(r,g,b,o){
this.red = r;
this.green = g;
this.blue = b;
this.opacity = o;
this.getColorString = function(){
return "rgba("+this.red+","+this.green+","+this.blue+","+this.opacity+")";
}
}
function Vector2d(x, y) {
this.x = x;
this.y = y;
this.add = function (vector2d) {
this.x += vector2d.x;
this.y += vector2d.y;
};
this.sub = function (vector2d) {
this.x -= vector2d.x;
this.y -= vector2d.y;
};
this.mult = function (mult) {
this.x *= mult;
this.y *= mult;
};
this.div = function (div) {
this.x /= div;
this.y /= div;
};
this.mag = function () {
return Math.sqrt(this.x * this.x, this.y * this.y);
};
this.norm = function () {
var m = this.mag();
if (m !== 0) {
this.div(m);
}
}
}
#background-canvas {
position: fixed;
width: 100%;
height: 100%
background-color:red;
top:0;
left:0;
}
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<canvas id="background-canvas" />
</body>
</html>

add rectangle dynamically on button click inside canvas

function Shape(x, y, w, h, fill) {
this.x = x || 0;
this.y = y || 0;
this.w = w || 1;
this.h = h || 1;
this.fill = fill || '#AAAAAA';
}
Shape.prototype.draw = function(ctx) {
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
Shape.prototype.contains = function(mx, my) {
return (this.x <= mx) && (this.x + this.w >= mx) &&
(this.y <= my) && (this.y + this.h >= my);
}
function CanvasState(canvas) {
this.canvas = canvas;
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
if (document.defaultView && document.defaultView.getComputedStyle) {
this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
this.stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
this.styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
this.styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
var html = document.body.parentNode;
this.htmlTop = html.offsetTop;
this.htmlLeft = html.offsetLeft;
this.valid = false;
this.shapes = [];
this.dragging = false;
this.selection = null;
this.dragoffx = 0;
this.dragoffy = 0;
var myState = this;
canvas.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false);
canvas.addEventListener('mousedown', function(e) {
var mouse = myState.getMouse(e);
var mx = mouse.x;
var my = mouse.y;
var shapes = myState.shapes;
var l = shapes.length;
for (var i = l-1; i >= 0; i--) {
if (shapes[i].contains(mx, my)) {
var mySel = shapes[i];
myState.dragoffx = mx - mySel.x;
myState.dragoffy = my - mySel.y;
myState.dragging = true;
myState.selection = mySel;
myState.valid = false;
return;
}
}
if (myState.selection) {
myState.selection = null;
myState.valid = false;
}
}, true);
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging){
var mouse = myState.getMouse(e);
myState.selection.x = mouse.x - myState.dragoffx;
myState.selection.y = mouse.y - myState.dragoffy;
myState.valid = false;
}
}, true);
canvas.addEventListener('mouseup', function(e) {
myState.dragging = false;
}, true);
canvas.addEventListener('dblclick', function(e) {
var mouse = myState.getMouse(e);
myState.addShape(new Shape(mouse.x - 10, mouse.y - 10, 20, 20, 'rgba(0,255,0,.6)'));
}, true);
this.selectionColor = '#CC0000';
this.selectionWidth = 2;
this.interval = 30;
setInterval(function() { myState.draw(); }, myState.interval);
}
CanvasState.prototype.addShape = function(shape) {
this.shapes.push(shape);
this.valid = false;
}
CanvasState.prototype.clear = function() {
this.ctx.clearRect(0, 0, this.width, this.height);
}
CanvasState.prototype.draw = function() {
if (!this.valid) {
var ctx = this.ctx;
var shapes = this.shapes;
this.clear();
var l = shapes.length;
for (var i = 0; i < l; i++) {
var shape = shapes[i];
if (shape.x > this.width || shape.y > this.height ||
shape.x + shape.w < 0 || shape.y + shape.h < 0) continue;
shapes[i].draw(ctx);
}
if (this.selection != null) {
ctx.strokeStyle = this.selectionColor;
ctx.lineWidth = this.selectionWidth;
var mySel = this.selection;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
this.valid = true;
}
}
CanvasState.prototype.getMouse = function(e) {
var element = this.canvas, offsetX = 0, offsetY = 0, mx, my;
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
offsetX += this.stylePaddingLeft + this.styleBorderLeft + this.htmlLeft;
offsetY += this.stylePaddingTop + this.styleBorderTop + this.htmlTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {x: mx, y: my};
}
function init() {
var s = new CanvasState(document.getElementById('canvas1'));
s.addShape(new Shape(40,40,50,50)); // The default is gray
}
<canvas id="canvas1" width="200" height="200" style="border:1px solid #000000;">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<button onclick= "init()">click </button>
On click of a button, I am creating a rectangular element which can be drag and drop inside the canvas element restricted area.
however, Is it possible to create a new rectangular element dynamically on every click of a button which can further be drag and drop inside the canvas boundary.
And the previous rectangular element won't lost.
Doing this from scratch will be a lot of work. I suggest you use a library like fabric.js which has everything you asked plus a lot more to offer. Here is a demo page.
On every button click you was calling init(),.. Causing you CanvasState to reset.
I've just made another function called addShape, and just called init once.
I've also kept a reference to the CanvasState, called s.. So I could do s.addShape.. So changes were very minimal.
Hope this helps..
function Shape(x, y, w, h, fill) {
this.x = x || 0;
this.y = y || 0;
this.w = w || 1;
this.h = h || 1;
this.fill = fill || '#AAAAAA';
}
Shape.prototype.draw = function(ctx) {
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
Shape.prototype.contains = function(mx, my) {
return (this.x <= mx) && (this.x + this.w >= mx) &&
(this.y <= my) && (this.y + this.h >= my);
}
function CanvasState(canvas) {
this.canvas = canvas;
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
if (document.defaultView && document.defaultView.getComputedStyle) {
this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
this.stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
this.styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
this.styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
var html = document.body.parentNode;
this.htmlTop = html.offsetTop;
this.htmlLeft = html.offsetLeft;
this.valid = false;
this.shapes = [];
this.dragging = false;
this.selection = null;
this.dragoffx = 0;
this.dragoffy = 0;
var myState = this;
canvas.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false);
canvas.addEventListener('mousedown', function(e) {
var mouse = myState.getMouse(e);
var mx = mouse.x;
var my = mouse.y;
var shapes = myState.shapes;
var l = shapes.length;
for (var i = l-1; i >= 0; i--) {
if (shapes[i].contains(mx, my)) {
var mySel = shapes[i];
myState.dragoffx = mx - mySel.x;
myState.dragoffy = my - mySel.y;
myState.dragging = true;
myState.selection = mySel;
myState.valid = false;
return;
}
}
if (myState.selection) {
myState.selection = null;
myState.valid = false;
}
}, true);
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging){
var mouse = myState.getMouse(e);
myState.selection.x = mouse.x - myState.dragoffx;
myState.selection.y = mouse.y - myState.dragoffy;
myState.valid = false;
}
}, true);
canvas.addEventListener('mouseup', function(e) {
myState.dragging = false;
}, true);
canvas.addEventListener('dblclick', function(e) {
var mouse = myState.getMouse(e);
myState.addShape(new Shape(mouse.x - 10, mouse.y - 10, 20, 20, 'rgba(0,255,0,.6)'));
}, true);
this.selectionColor = '#CC0000';
this.selectionWidth = 2;
this.interval = 30;
setInterval(function() { myState.draw(); }, myState.interval);
}
CanvasState.prototype.addShape = function(shape) {
this.shapes.push(shape);
this.valid = false;
}
CanvasState.prototype.clear = function() {
this.ctx.clearRect(0, 0, this.width, this.height);
}
CanvasState.prototype.draw = function() {
if (!this.valid) {
var ctx = this.ctx;
var shapes = this.shapes;
this.clear();
var l = shapes.length;
for (var i = 0; i < l; i++) {
var shape = shapes[i];
if (shape.x > this.width || shape.y > this.height ||
shape.x + shape.w < 0 || shape.y + shape.h < 0) continue;
shapes[i].draw(ctx);
}
if (this.selection != null) {
ctx.strokeStyle = this.selectionColor;
ctx.lineWidth = this.selectionWidth;
var mySel = this.selection;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
this.valid = true;
}
}
CanvasState.prototype.getMouse = function(e) {
var element = this.canvas, offsetX = 0, offsetY = 0, mx, my;
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
offsetX += this.stylePaddingLeft + this.styleBorderLeft + this.htmlLeft;
offsetY += this.stylePaddingTop + this.styleBorderTop + this.htmlTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {x: mx, y: my};
}
var s;
function init() {
s = new CanvasState(document.getElementById('canvas1'));
}
function addShape() {
s.addShape(new Shape(40,40,50,50)); // The default is gray
}
init();
<canvas id="canvas1" width="400" height="300" style="border:1px solid #000000;">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<button onclick= "addShape()">click </button>

Polymorphic Behavior in Javascript

I am trying to create a base class called Sprite and then child classes of whatever, in this case "Dog."
I want the objects to have all the same properties like x, y, width, height, but have different behaviors for methods, like this one called mouseEventListener.
I cannot seem to get the code to work correctly.
function Sprite(imgg,w,h)
{
this.img = imgg;
this.x = 350;//Math.random()*700;
this.y = 350;//Math.random()*700;
this.vx = 0;//Math.random()*8-4;
this.vy = 0;//Math.random()*8-4;
this.width = w;
this.height = h;
this.rotatespeed = 0.00;
this.rotate = 40;
}
Sprite.prototype.drawSprite = function(ctx)
{
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.rotate);
ctx.drawImage(this.img,0,0,this.img.width,this.img.height,-this.width/2,-this.height/2,this.width,this.height);
ctx.restore();
}
Sprite.prototype.mouseEventListener = function(evt, type)
{
console.log("In Sprite");
}
Dog.prototype.mouseEventListener = function(evt,type)
{
console.log("In Dog");
}
Here is the full code:
/**
How to use this library
1.
*/
function Sprite(imgg,w,h)
{
this.img = imgg;
this.x = 350;//Math.random()*700;
this.y = 350;//Math.random()*700;
this.vx = 0;//Math.random()*8-4;
this.vy = 0;//Math.random()*8-4;
this.width = w;
this.height = h;
this.rotatespeed = 0.00;
this.rotate = 40;
}
Sprite.prototype.drawSprite = function(ctx)
{
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.rotate);
ctx.drawImage(this.img,0,0,this.img.width,this.img.height,-this.width/2,-this.height/2,this.width,this.height);
ctx.restore();
}
Sprite.prototype.updateSprite = function()
{
this.x += this.vx;
this.y += this.vy;
this.rotate += this.rotatespeed;
if(this.x > 700)
this.vx = -this.vx;
if(this.x < 0)
this.vx = -this.vy;
if(this.y > 700)
this.vy = -this.vy;
if(this.y < 0)
this.vy = -this.vy;
}
Sprite.prototype.mouseEventListener = function(evt, type)
{
console.log("In Sprite");
}
Dog.prototype = new Sprite();
Dog.prototype.mouseEventListener = function(evt,type)
{
console.log("In Dog");
}
//------------------------------------------
//GLOBAL VARIALBES
var setIntervalAmount = 30;
var scrollAmount = 0.5;
var game;
function Camera()
{
this.x = -350;
this.y = -350;
this.rotate = 0;
this.scale = 0;
this.scale = 1;
this.previousX = 0;
this.previousY = 0;
this.drag = false;
}
function Game()
{
this.camera = new Camera();
this.canvas = document.createElement("canvas");
document.body.appendChild(this.canvas);
this.canvas.id="mycanvas";
this.canvas.width = 700;
this.canvas.height = 700;
this.context = this.canvas.getContext("2d");
this.objects = new Array();
}
Game.prototype.gameLoop = function()
{
this.context.clearRect(0,0,this.canvas.width, this.canvas.height);
this.context.save();
this.context.translate(this.canvas.width/2, this.canvas.height/2);
this.context.scale(this.camera.scale,this.camera.scale);
this.context.rotate(this.camera.rotate);
this.context.translate(this.camera.x,this.camera.y);
for(var i=0;i<this.objects.length;i++)
{
this.objects[i].updateSprite();
this.objects[i].drawSprite(this.context);
}
this.context.restore();
}
Game.prototype.handleMouse = function(evt,type)
{
var mouseX = event.clientX - this.context.canvas.offsetLeft;
var mouseY = event.clientY - this.context.canvas.offsetTop;
var canvasX = mouseX * this.context.canvas.width / this.context.canvas.clientWidth;
var canvasY = mouseY * this.context.canvas.height / this.context.canvas.clientHeight;
// canvasX = this.camera.scale / canvasX;
// canvasY = this.camera.scale / canvasY;
canvasX /= this.camera.scale;
canvasY /= this.camera.scale;
if(type == "move")
{
if(this.camera.drag == true)
{
this.camera.x -= this.camera.previousX-canvasX;
this.camera.y -= this.camera.previousY-canvasY;
}
this.camera.previousX = canvasX;
this.camera.previousY = canvasY;
}
if(type =="down")
{
this.camera.drag = true;
}
if(type == "up")
{
this.camera.drag = false;
}
for(var i=0;i<this.objects.length;i++)
{
this.objects[i].mouseEventListener(evt,type);
}
};
Game.prototype.mouseWheelListener = function(evt)
{
if(evt.wheelDelta < 0)
game.camera.scale /= (1+scrollAmount);
else
game.camera.scale *= (1+scrollAmount);
}
function mouseWheelListener()
{
var evt = window.event;
game.mouseWheelListener(evt);
}
function mouseDownListener()
{
var evt = window.event;
var type = "down"
game.handleMouse(evt,type);
}
function mouseUpListener()
{
var evt = window.event;
var type = "up"
game.handleMouse(evt,type);
}
function mouseMoveListener()
{
var evt = window.event;
var type = "move"
game.handleMouse(evt,type);
}
//------------------
window.addEventListener('load',function(event){startgame();});
var dog = new Image();
dog.src = "dog.png";
function startgame()
{
game = new Game();
for(var i=0;i<1;i++)
game.objects.push(new Dog(dog,250,250));
setInterval(function(){game.gameLoop()},setIntervalAmount);
document.getElementById("mycanvas").addEventListener("wheel", mouseWheelListener);
document.getElementById("mycanvas").addEventListener("mousedown", mouseDownListener);
document.getElementById("mycanvas").addEventListener("mouseup", mouseUpListener);
document.getElementById("mycanvas").addEventListener("mousemove", mouseMoveListener);
}
I found the answer on a Youtube video...
Dog.prototype = Object.create(Sprite.prototype);
That is the line of code that I need.

Try to implement shooting by bullets

Hello I try to implement shooting by drawing bullets. I created function SHOOt and just called it from the keyboard detection condition.... However, when i press key to shoot everything is stopped and no shooting, no bullets....Whats happening?! Any help appreciated thanks.
<html>
<head>
<title>Spaceman Invaders</title>
<script>
window.onload = function() {
var posx = 20;
var posy = 0;
var go_right = true;
var go_down = false;
var canvas = document.getElementById("screen");
context = canvas.getContext("2d");
context2 = canvas.getContext("2d");
var Alien = function(x, y) {
this.x = x;
this.y = y;
this.posx = 30 + x*30;
this.posy = 90 + y*30;
this.go_right = true;
this.go_down = false;
}
function Player() {
this.x=0, this.y = 0, this.w = 20, this.h = 20;
this.render = function (){
context.fillStyle = "orange";
context.fillRect(this.x, this.y, this.w, this.h);
}
}
var X2=223;
var Y2=320;
function shoot(){
context2.fillStyle = "white";
context2.fillRect = (X2, Y2--, 5,10);
context2.fillStyle = "yellow";
context2.fillRect = (X2, Y2, 5,10);
if (Y2>=0) {
timer=setTimeout("shoot()", 5);
}
}
var player = new Player();
Alien.prototype.move = function() {
if (!this.go_down) {
if(this.posx + (2-this.x) * 30 < 250 && this.go_right) {
this.posx += 3;
} else if(this.posx < 30 + this.x*30) {
this.go_right = true;
this.go_down = true;
} else if(!this.go_right) {
this.posx -= 3;
} else {
this.go_right = false;
this.go_down = true;
}
} else {
//if(posy <= 30)
this.posy += 30;
this.go_down = false;
}
}
Alien.prototype.draw = function(context) {
if(this.x == 0) {
context.fillStyle = "red";
} else if(this.x == 1) {
context.fillStyle = "yellow";
} else {
context.fillStyle = "blue";
}
context.beginPath();
context.fillRect(this.posx, this.posy, 20 , 20);
context.fill();
}
var canvas = document.getElementById("screen");
context = canvas.getContext("2d");
if (canvas.getContext) {
//init the aliens array
var aliens = [];
for(var i = 0; i < 3; i++) {
for(var j = 0; j < 3; j++) {
aliens.push(new Alien(j, i));
}
}
player.x=100;
player.y= 480;
setInterval( function() {
context.fillStyle="black";
context.fillRect(0,0,canvas.width, canvas.height);
/*context.fillStyle = "white";
context.fillRect(100, 460, 30 , 30);*/
player.render();
//move all aliens & draw all aliens
for(var i = 0; i < 9; i++) {
aliens[i].move(),
aliens[i].draw(context);
}
}, 20);
document.addEventListener('keydown', function(event){
var key_press = String.fromCharCode(event.keyCode);
// alert(event.keyCode + " | " + key_press);
if (key_press == "D") {
if (player.x >=(280)){
player.x=(280);
}
else {
player.x +=3;
}
} else
if (key_press=="A"){
if (player.x<0){
player.x=(0);
}
else {player.x -=3;}
} else
if (key_press="W") {
//alert("Pah-pah");
shoot();
}
});
}
};
</script>
</head>
<body>
<canvas id="screen" width="300" height="500"/>
</body>
</html>
In your shoot() function, you're setting fillRect to the parameters you're meaning to pass to fillRect().
function shoot(){
context2.fillStyle = "white";
//context2.fillRect = (X2, Y2--, 5,10); -- This is a bad line. Correct:
context2.fillRect(X2, Y2--, 5,10);
context2.fillStyle = "yellow";
//context2.fillRect = (X2, Y2, 5,10); -- This is a bad line. Correct:
context2.fillRect(X2, Y2, 5,10);
if (Y2>=0) {
timer=setTimeout("shoot()", 5);
}
}
Has some weird behavior, and there's a lot that could be improved and cleaned up here, but this should get you on the right track.
And for your future reference, if you're using Chrome, open up the Dev Tools (CTRL/CMD + SHIFT + J) and you could see the error:
Uncaught TypeError: Property 'fillRect' of object #<CanvasRenderingContext2D> is not a function
That clued me in to know it was getting overwritten somewhere, as we know it IS normally a function of the CanvasRenderingContext2D.

Categories