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

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!

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

Creating a grid which only exists within a shape

am having trouble doing something in Canvas - I want to create a grid that only exists within that shape. I am able to create the gird to fit the shape if it is a square but if the shape is an unusual shape I do not know how to create the grid.
Here is what I have so far: I only want the grid to exist within the shape
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(40, 65);
ctx.lineTo(100, 105);
ctx.lineTo(200, 15);
ctx.fillStyle = 'green';
ctx.fill();
function drawGrid(context) {
context.globalCompositeOperation = 'destination-out ';
for (var x = 40.5; x < 300; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 300);
}
for (var y = 0.5; y < 501; y += 10) {
context.moveTo(0, y);
context.lineTo(300, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
drawGrid(ctx)
https://jsfiddle.net/fom9gtb6/
context.globalCompositeOperation = 'destination-out ';
should be
context.globalCompositeOperation = 'destination-out';
You can use context.globalCompositeOperation = 'source-atop';
From MDN
The new shape is only drawn where it overlaps the existing canvas content.
This way the strokeStyle is actually used for the grid instead of erasing parts of the shape, which destination-out will do.

creating new canvas animations on click

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.

Change color of canvas element

I am trying to change the color of line drawn on canvas dynamically...
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Grey"
It could be mouseover event or press botton or mouse click event, I want to change the color of line or make it bold. Is it possible to change the color by adding event or is it possible to give style on an event on particular element?
Very close. In a sense, you can't really "change" the color of an element on the canvas because it has no scene graph, or, in other words, it has no history of what has been drawn on the canvas. To change the color of a line, you would have to redraw the line.
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Grey";
ctx.stroke();
// To make the line bold and red
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Red";
ctx.lineWidth = 5;
ctx.stroke();
If the canvas had a more complex scene going on, you would have to redraw the entire scene. There are numerous Javascript libraries that extend the base features of the canvas tag, and provide other drawing capabilities. You may want to take a look at Processing, it looks quite impressive.
I was having the same problem, I did it by moving another line with another color of different canvas element, so it gives appearance of line changing its color dynamically.
function drawGreyLine() {
ctx1.clearRect(0, 0, WIDTH, HEIGHT);
ctx1.strokeStyle = "Grey"; // line color
ctx1.moveTo(0, 0);
ctx1.moveTo(0, 200);
ctx1.lineTo(200, 200);
}
function drawColorLine() {
x += dx;
if (x <= 200) {
ctx2.beginPath();
ctx2.lineWidth = 5;
ctx2.lineCap = "round";
ctx2.strokeStyle = "sienna"; // line color
ctx2.moveTo(0, 200);
ctx2.lineTo(x, 200);
ctx2.moveTo(200, 200);
ctx2.stroke();
}
}
Hope this solves your problem.... :)
var canvas = document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var line1={x:10,y:10, l:40, h:1}
var down=false;
var mouse={x:0,y:0}
canvas.onmousemove=function(e){ mouse={x:e.pageX-this.offsetLeft,y:e.pageY-this.offsetTop};
this.onmousedown=function(){down=true};
this.onmouseup=function(){down=false} ;
}
setInterval(function(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.beginPath()
ctx.moveTo(line1.x,line1.y)
ctx.lineTo(line1.x +line1.l,line1.y)
ctx.lineTo(line1.x +line1.l,line1.y+line1.h)
ctx.lineTo(line1.x,line1.y+line1.h)
ctx.isPointInPath(mouse.x,mouse.y)? (ctx.fillStyle ="red",line1.x=down?mouse.x:line1.x,line1.y=down?mouse.y:line1.y):(ctx.fillStyle ="blue")
ctx.fill()
},100)

Is there a way to tile a background image over a canvas path?

For example, I have a canvas in a page with some kind of a path. It is created by javascript this way:
var context = $('#some_canvas').getContext('2d');
context.beginPath();
context.lineWidth = 5;
context.strokeStyle = '#000000';
context.moveTo(0, 0);
context.lineTo(100, 100);
context.stroke();
Is there a way to make the path that appears after this command to have some tiled background image?
I believe you're looking for the createPattern() method:
var pattern = new Image;
pattern.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAIAAABbzbuTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZtJREFUeNqMUs+rAVEUvjNG2MhClOUsppSEjbWFpOwtMIv5B2ZhZqkkZKkpe+XPYKtsbSxsZIGaQpgi4r3Pu5p3eV45dW/nnPt959zzg5RKpU6n8/WxkHq9LghCs9n8lICjKAohpFwuf0LgcCzLSqfTo9FIVVVd10He7XbX6/V8Pu/3e47jcB+Px0AgkEqlCOXNZjNJkgB1/wg+6XA4ACXPomkasXM1Gg3yj4AZi8WAHgwGgu1dLpcvOKRKJBLZbDaTyYDgdDrvXjtDLpd7yT6dTv8WzdNnaPP53A6Mezgc+v3+N/+jvO12GwwGqQfFYJRQksnker1+MwfIZDLxeDwA+Xy+xWIBT6VSgYk+Hg4HlvAoerVaodNQ8vl8KBSCUqvVAG2326g4EolsNhuYGNQjQ6/XAwh9GI/HbDxZltn/o2OPDBgctaPRKIsQRZEqWJxisXg3aaRCoQBvv99nw59Op3A4DH+1Wu12u09FYwh4w/6wBGwUOhuPx6FfLpfb7fZbtGEYpmnyPM/+x+v1tlotl8sFHdtFnd8CDACAg6Y2weEZQQAAAABJRU5ErkJggg==";
pattern.onload = function () {
var context = $('#some_canvas').getContext('2d');
context.beginPath();
context.lineWidth = 16;
context.strokeStyle = context.createPattern(pattern, 'repeat');
context.moveTo(0, 0);
context.lineTo(150, 150);
context.stroke();
};

Categories