creating new canvas animations on click - javascript

I am a beginner in canvas and I got stuck in creating multiple images in canvas. I created a ball which bounces inside canvas. Now I want to create a second ball when I click somewhere in the canvas. If my question is too dump please spare me. Here is the code.
The animate function gets executed onpage load:
function animate()
{
// do something
setInterval(function(){drawBall(ball_props,color,null);},10);
}
function drawBall(ball_props,color)
{
var c = document.getElementById("bouncy_ball");
var context = c.getContext("2d");
context.clearRect(0,0,c.width,c.height);
trajactory(ball_props);
context.beginPath();
context.arc(ball_props.center_x,ball_props.center_y,radius,0,Math.PI*2,false);
context.stroke();
context.fillStyle = color;
context.fill();
context.lineWidth = "2";
context.lineCap = "round";
context.strokeStyle = "black";
context.stroke();
boundaryConditions(ball_props);
accelerator(ball_props);
};
This helped me in creating the first ball in my canvas animation. Now i have a click function which is supposed to trigger the second ball but instead its deleting the first ball and creating a new one.
function onclickingCanvas()
{
setInterval(function(){drawBall(ball2_props,color2);},10);
}
Could you please help me fix this.Thanks in advance.

This line means that you clear your canvas each time you call "drawball" function :
context.clearRect(0,0,c.width,c.height);
So if you want to draw a second ball, you should call "drawball" once but with an array containing all the balls... something like that :
var ball1 = {
ball_props: ball_props,
color: color
}, ball2 = {
ball_props: ball2_props,
color: color2
};
var balls = [ball1];
function animate(){
setInterval(function(){drawBall(balls);},10);
}
function drawBall(balls)
{
var c = document.getElementById("bouncy_ball");
var context = c.getContext("2d");
context.clearRect(0,0,c.width,c.height);
for(var i=0; i<balls.length; i++){
trajactory(balls[i].ball_props);
context.beginPath();
context.arc(balls[i].ball_props.center_x,balls[i].ball_props.center_y,radius,0,Math.PI*2,false);
context.stroke();
context.fillStyle = balls[i].color;
context.fill();
context.lineWidth = "2";
context.lineCap = "round";
context.strokeStyle = "black";
context.stroke();
boundaryConditions(balls[i].ball_props);
accelerator(balls[i].ball_props);
}
};
function onclickingCanvas(){
balls.push(ball2);
}
Haven't tested it... but well you have the idea now.

Related

HTML Canvas Interval, CanvasPattern dissapears

When creating an HTML canvas I was planning on making these cylinders and animating marbles moving inside them. However, when trying to do so it would just delete everything. After messing around with my code, I discovered the problem was due to the fillStyle which was a CanvasPattern from an image.
This snippet simulates exactly what I am experiencing. The rectangle draws perfectly fine, however, after 1 second, when the interval runs, it disappears and there is no arc or "marble" drawn. There are no errors in console either
With Interval (Not working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
When I get rid of the interval it would work, but when the interval is there, nothing is drawn.
I have absolutely no idea why this is happening and I cannot find anything on the internet regarding this problem. Is there a way I can animate this marble while having the image continue to mask its fillStyle??
Without Interval (Working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
//var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
//}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
Things I've tried:
Got rid of beginPath and closePath, doesn't make anything disappear but doesn't display arc
Recreating pattern inside the interval
Making the fillstyle a colour for everything (Works)
Making the fillstyle of the marble a colour (Doesnt work)
EDIT: After looking some more, I believe the problem is in the globalCompositeOperation. It's what deals with the pattern intersecting the drawing. When looking at all the types, source-in is the only one that satisfies my expected result, however, it's not working in this situation weirdly.
Thank you in advance
The problem is your ctx.globalCompositeOperation instruction. Using source-in, you're explicitly telling the canvas to make anything that's a different color from the new thing you're drawing (on a per pixel basis) transparent. Since every pixel is different, everything becomes transparent and you're left with what looks like an empty canvas (even if the ImageData will show RGBA data in which the RGB channels have meaningful content, but A is 0).
Remove the globalCompositeOperation rule and you're good to go, but you should probably take some time to rewrite the logic here, so that nothing happens until your image is loaded, because your code is pretty dependent on that pattern existing: wait for the image to load, the build the pattern, assign it to the context, and then start your draw loop.
const canv = document.getElementById("canvas");
const ctx = canv.getContext('2d');
let x = 60, y = 20;
function start() {
const matte = new Image(canv.width, canv.height);
matte.addEventListener(`load`, evt =>
startDrawing(ctx.createPattern(matte, 'repeat'))
);
matte.addEventListener(`load`, evt =>
console.error(`Could not load ${matte.src}...`);
);
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
}
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
setInterval(() => {
draw();
y += 10;
}, 1 * 1000);
}
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// and kick everything off
start();
Although on another note, normally setInterval is not the best choice for animations: you usually want requestAnimationFrame instead, with a "wall time" check (e.g. doing things depending on the actual clock, instead of trusting intervals):
...
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
startAnimation();
}
let playing, lastTime;
function startAnimation() {
playing = true;
lastTime = Date.now();
requestAnimationFrame(nextFrame);
}
function stopAnimation() {
playing = false;
}
function nextFrame() {
let newTime = Date.now();
if (newTime - lastTime >= 1000) {
draw();
}
if (playing) {
lastTime = newTime;
requestAnimationFrame(nextFrame);
}
}
...
https://jsbin.com/wawecedeve/edit?js,output

How to move box without clearRect() in JS' canvas better?

everyone. I am going to develop a game like Piano Tiles in JS.
The problem I encounter is that I need to let several tiles moving which might appear in different times, it let me can't use clearRect() and then draw next position's tiles, if I do so, my multiple tiles will twinkle(because when A tiles call clearRect, B tiles will disappear, which is shouldn't happen)
The solution I figure out is not to use clearRect() to clear whole screen ,I
just clear Rectangle where I don't want, and then fillRect(its fill style is same as my background color) to fill this little empty rectangle again.
My solution can almost reach my purpose, but there still some flaws in my code. When my black tiles moving, there still are some tiny gray rectangle appear (and disappear soon).
I want to know are there any better way can I move multiple box(or rectangle) ?
PS: my background is using gradient so it probably make my problem more tough.
Following is my code:
myTiles store two tiles, paintWindow function is aim at drawing background, my solution is written in move() function
var c = document.getElementById("piano");
var context = c.getContext("2d");
startGame();
function startGame(){
paintWindow();
myTiles = [];
myTiles[0] = new Block(0);
myTiles[1] = new Block(1);
}
function paintWindow(){
my_gradient = context.createLinearGradient(0,0,0,600);
my_gradient.addColorStop(0,"rgba(65,234,246,0.6)");
my_gradient.addColorStop(1,"rgba(254,74,251,0.5)");
context.fillStyle = my_gradient;
context.fillRect(0,0,300,600);
context.beginPath();
context.moveTo(72,0);
context.lineTo(72,600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(148,0);
context.lineTo(148,600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(226,0);
context.lineTo(226,600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(0,470);
context.lineTo(300,470);
context.strokeStyle = "white";
context.stroke();
}
function Block(index){
this.index = index;
this.appearPos = Math.floor(Math.random()*4);
this.width = 70;
this.height = 120;
this.color = "black";
switch(this.appearPos){
case 0:
this.x = 0;
this.y = 0;
break;
case 1:
this.x = 75;
this.y = 0;
break;
case 2:
this.x = 152;
this.y = 0;
break;
case 3:
this.x = 228;
this.y = 0;
break;
}
context.fillStyle = this.color;
context.fillRect(this.x,this.y,this.width,this.height);
this.interval = setInterval(move,10,this.index);
}
function move(index){
//context.clearRect(0,0,300,600);
//paintWindow();
myTiles[index].y += 1;
context.fillStyle = "black";
context.fillRect(myTiles[index].x,myTiles[index].y,70,120);
context.clearRect(myTiles[index].x,myTiles[index].y-2,70,2);
context.fillStyle = my_gradient;
context.fillRect(myTiles[index].x,myTiles[index].y-2,70,2);
}
I would suggest you not to create a new interval for each new block, but rather create one interval in which you update all the blocks at once. This might look something like this:
setInterval(moveAll,10);
function moveAll(){
context.clearRect(0,0,c.width,c.height);
for (var i=0;i<myTiles.length;i++){
move(i);
}
}
If you can't do that for some reason, you could also draw the tiles in a separate canvas so the background is preserved when clearing a tile:
<div>
<canvas id="background" style="position:absolute;left:0;top:0;">
<canvas id="tiles" style="position:absolute;left:0;top:0;">
</div>
That way the second canvas would be displayed over the first one, but the background would still be visible behind it where it is transparent.
Just use CSS for the gradient background. See this fiddle. Pay attention to the CSS:
<style>
#piano {
background: linear-gradient(rgba(65,234,246,0.6), rgba(254,74,251,0.5));
}
</style>
Also get rid of everything about the gradient background in your paintWindow function:
function paintWindow() {
context.beginPath();
context.moveTo(72, 0);
context.lineTo(72, 600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(148, 0);
context.lineTo(148, 600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(226, 0);
context.lineTo(226, 600);
context.strokeStyle = "white";
context.stroke();
context.beginPath();
context.moveTo(0, 470);
context.lineTo(300, 470);
context.strokeStyle = "white";
context.stroke();
}
That's all!

Trouble with canvas restore() javascript

I am wanting to create a canvas scene that involves drawing lines. Literally, to give the appearance of solid lines being drawn from one x/y coordinate to anther x/y coordinate. My trouble seems to be with my use of save() and restore(). The way I understand it is, if I save() my canvas before I begin drawing, I can then call on restore() to reset my canvas back to that beginning state. In this way, I can begin my next line without a distorted canvas.
When the code below is run, the first line is drawn as intended. I then call restore() to allow me to work with a non-distorted canvas for the next line. As a result (so it seems) the second line is drawn as instructed. I again call on restore() to allow me to draw the third line from one specified set of coordinates to another. However, this third line is not starting at the coordinates given. It’s as if the canvas is still distorted from the previous line, but I can’t understand as to why. Can anyone shed some light on my dilemma? (Also, it there is an easier to way to create line drawings of this style, for the web, could you let me know?)
var canvas = document.getElementById('canvas');
var c = canvas.getContext('2d');
c.save();
var a = 0;
function callVerticalTeal() {
if(a < 200) { //draw line the length of 200px
drawVerticalTeal();
a++;
setTimeout(callVerticalTeal, 0);
}
setTimeout(callHorizontalRed, 1200);
}
function drawVerticalTeal(){
c.transform(1,0,0,1,0,1);
c.beginPath();
c.moveTo(325, 200);
c.strokeStyle = 'teal';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 200);
c.stroke();
}
// Start the loop
setTimeout(callVerticalTeal, 0);
var b = 0;
function callHorizontalRed() {
if(b < 200) {
drawHorizontalRed();
b++;
setTimeout(callHorizontalRed, 1000);
}
c.restore();
setTimeout(callHorizontalBlack, 1200);
}
function drawHorizontalRed(){
c.restore();
c.transform(1,0,0,1,1,0);
c.beginPath();
c.moveTo(325, 200);
c.strokeStyle = 'brown';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 200);
c.stroke();
}
var x = 0;
function callHorizontalBlack() {
if(x < 200) {
draw();
x++;
setTimeout(call, 5000);
}
setTimeout(callVerticalBlack, 1200);
}
function draw(){
c.restore();
c.transform(1,0,0,1,1,0);
c.beginPath();
c.moveTo(325, 400);
c.strokeStyle = 'black';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 400);
c.stroke();
}
You call context.save() only once. Typically context.save() is called first in any drawing method, and context.restore() is the last call. So it is an interceptor, if you want to call it so.
function paintSomething() {
ctx.save();
// now paint something
ctx.restore(); // we now are clean again, because we have the previously saved state
}

Trying to make every other house's door green

What my task is, is to draw a house then use a loop (at the bottom) to fill the canvas with the houses instead of manually placing each one.
Well I'm trying to work how to make every other house's door green. I've been trying to hack away at code for awhile now but I cannot figure it out.
I know all my house pieces should be in functions and what not but that is a later task for the work I am doing! Here's my code:
//Check to see if the browser supports
//the addEventListener function
if(window.addEventListener)
{
window.addEventListener
(
'load', //this is the load event
onLoad, //this is the evemnt handler we going to write
false //useCapture boolen value
);
}
//the window load event handler
function onLoad()
{
var canvas;
var context;
//this function will intialise our variables
function initialise()
{
// Fune the canvas element using its id attribute.
canvas = document.getElementById('canvas');
//if it couldn't be found
if (!canvas)
{
//make a message bok appear with an error message
alert('Error: i cannot find this "canvas" of which you speak. Please re-evaluate your life choices.');
return;
}
//check if there is any getContext function
if(!canvas.getContext)
{
alert('Error no cavnas.getContext could be found');
return;
}
//get the 2D canvas context.
context = canvas.getContext('2d');
if(!context)
{
alert('Error failed to getCOntext');
return;
}
canvas.addEventListener("mousedown", getPosition, false);
}
// this is a little help tool for me as i'm awful at working out co-ordinates
function getPosition(e)
{
var x = e.x;
var y = e.y;
x -=canvas.offsetLeft;
y -=canvas.offsetTop;
alert("x:" +x+ "y:"+y);
}
//this function will draw on the canvas for me!
function draw()
{
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
}
// pX and pY are the parameters are going to be used so that the inside of the house body becomes the drawing canvas.
function drawHouse(pX ,pY)
{
//body
context.beginPath();
context.fillStyle = '#ffffff';
context.strokeStyle = "black";
context.lineWidth = "5";
context.rect(pX,pY, 160,110);
context.closePath();
context.fill();
context.stroke();
//roof
context.beginPath();
context.fillStyle = "red";
context.moveTo(pX,pY-1);;
context.lineTo(pX+80, pY-95);
context.lineTo(pX+160, pY-1);
context.closePath();
context.fill();
context.stroke();
//door
context.beginPath();
context.fillStyle = "green";
context.fillSStyle = "red";
context.strokeStyle = "black";
context.rect(pX+55,pY+30, 50, 80);
context.fill();
context.stroke();
//handle
var radius = 5;
context.beginPath();
context.arc(pX+93, pY+75, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'purple';
context.fill();
context.lineWidth = 3;
context.strokeStyle = 'black';
context.stroke();
//window Left
context.beginPath();
context.fillStyle = 'blue';
context.strokeStyle = "black";
context.rect(pX+12,pY+30, 30, 60);
context.fill();
context.stroke();
//window Filler left vertically
context.beginPath();
context.moveTo(pX+26.5,pY+30);
context.lineTo(pX+26.5, pY+90);
context.fill();
context.stroke();
//Window filler left horizontally
context.beginPath();
context.moveTo(pX+41, pY+60);
context.lineTo(pX+11,pY+60);
context.fill();
context.stroke();
//Window Right
context.beginPath();
context.fillStyle = 'blue';
context.strokeStyle = "black";
context.rect(pX+117,pY+30, 30, 60);
context.fill();
context.stroke();
//window filler right vertically
context.beginPath();
context.moveTo(pX+131.5,pY+30);
context.lineTo(pX+131.5, pY+90);
context.fill();
context.stroke();
//window Filler right horizontally
context.beginPath();
context.moveTo(pX+147, pY+60);
context.lineTo(pX+117,pY+60);
context.fill();
context.stroke();
}
initialise();
draw();
for(var i=0; i < 5; i++)
{
var pX=0+160*i;
for(var b=0; b < 5; b++)
{
var pY=100+210*b;
drawHouse(pX,pY);
}
}
}
Add a parameter to drawHouse:
function drawHouse(pX, pY, drawGreen) {
// ...
if (drawGreen)
context.fillStyle = "green";
else
context.fillSStyle = "red";
// ...
}
And then, pass it accordingly. For example:
// before your loop
var paintGreen = false;
// In the inner for
var pY=100+210*b;
drawHouse(pX,pY,paintGreen);
paintGreen = !paintGreen; // alternate

HTML5 Canvas - How to fill() a specific context

I'm trying to make a website where an image is drawn on Canvas, then later the user is able to press a button to ctx.fill() certain parts of it with color. I'm running into issues where I can only ctx.fill() the most recently created shape which often isn't the shape I want.
Here's an example. In this code (live at http://build.rivingtondesignhouse.com/piol/test/) I'm trying to draw the first rectangle, then save() it on the stack, then draw the second rectangle (and don't save it), then when my fill() function is called I want to restore() the first rectangle and ctx.fill() it with a different pattern. It doesn't work!
In practice, I'm actually trying to fill the gray part of this complex shape with any color the user chooses AFTER the image has been drawn, but I think the technique is the same. (http://build.rivingtondesignhouse.com/piol/test/justTop.html)
Thanks in advance for any help!!!
Here's the code:
<script type="text/javascript">
var canvas;
var ctx;
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
draw();
}
function draw() {
ctx.fillStyle = '#FA6900';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(0,0,15,150);
ctx.save();
ctx.fillStyle = '#E0E4CD';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(30,0,30,150);
}
function fill(){
var image = new Image();
image.src = "http://www.html5canvastutorials.com/demos/assets/wood-pattern.png";
image.onload = drawPattern;
function drawPattern() {
ctx.restore();
ctx.fillStyle = ctx.createPattern(image, "repeat");
ctx.fill();
}
}
init();
There are a few misunderstands that we need to clear up before I can answer the question.
save() and restore() do not save and restore the canvas bitmap. Instead they save and restore all properties that are set on the canvas context and that's all!
For example
ctx.fillStyle = 'red';
ctx.save(); // save the fact that the fillstyle is red
ctx.fillStyle = 'blue'; // change the fillstyle
ctx.fillRect(0,0,5,5); // draws a blue rectangle
ctx.restore(); // restores the old context state, so the fillStyle is back to red
ctx.fillRect(10,0,5,5); // draws a red rectangle // draws a red rectangle
See that code live here.
So you aren't saving a rectangle (or anything drawn) by calling save(). The only way you you can save the bitmap is by drawing it (or part of it) to another canvas (using anotherCanvasContext.drawImage(canvasIWantToSave, 0, 0)) or by saving enough information that you can redraw the entire scene with the appropriate changes.
Here is an example of one way you could re-structure your code so that it does what you want: http://jsfiddle.net/xwqXb/

Categories