I want to create a break out game through javascript. I am wondering why the ctx.clearRect does not working. I want to put the rectangle in the y coordinate 430 to make it show at the bottom of the canvas. It moves when I have used the window.setInterval. But the rectangle move continuously.
Any help would be appreciated. Sorry for my poor English.
var canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
var position = 0;
var yposition = 430;
var length = 80;
var width = 20;
var xSpeed = length*1;
var ySpeed = 0;
function R(){
ctx.fillStyle = "green";
ctx.fillRect(position, yposition, length, width);
};
function C(){
position += xSpeed;
yposition += ySpeed;
};
window.setInterval(() => {
ctx.clearRect(0, 430, length, width);
R();
C();
},150);
ctx.beginPath();
ctx.arc(150, 50, 20, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
The culprit are the parameters you're feeding into the clearRect function:
(0, 430, length, width)
Since length and width are hardcoded values of 80 and 20 respectively, the above means every time the intervals callback function gets fired it clears a rectangular area of 80 x 20 pixels at x = 0 and y = 430.
As your green paddle is moving you're actually clearing an area your paddle isn't located at anymore.
So you basically have two options:
Clear the whole canvas every frame
Clear the screen area your paddle has been before changing it's position
The second would look a little something like this:
var canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
var position = 0;
var yposition = 150;
var length = 80;
var width = 20;
var xSpeed = length * 1;
var ySpeed = 0;
function R() {
ctx.fillStyle = "green";
ctx.fillRect(position, yposition, length, width);
}
function C() {
position += xSpeed;
yposition += ySpeed;
}
window.setInterval(() => {
ctx.clearRect(position, yposition, length, width);
C();
R();
}, 500);
<canvas id="canvas" width="600" height="400"></canvas>
I'd definitely recommend clearing the whole canvas though since there will be other on-screen objects beside the paddle.
Related
This animation (based on the answer of
Вася Воронцов) loads the computer very much. I do this animation in canvas. Animation loads proccesor very much. Here the light follows the cursor and leaves traces. Animation works correctly but proccesor loads very much.
Deleting and changing the radii of circles is done by saving their coordinates.
The effect is controlled by changing the variables radius (circle radius), period (time for which the circle disappears), color (circle color), blur (blur radius) and cursor radius (pointer circle radius).
How to optimize this animation so that it loads the computer less?
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = document.body.offsetWidth;
var height = document.body.offsetHeight;
var points = [],
cursor = [-10, -10];
var t = 0;
var radius = 100;
var period = 2100;
var color = "rgba(239, 91, 59, .5)";
var blur = 600;
canvas.style.width = canvas.width = width;
canvas.style.height = canvas.height = height;
context.fillStyle = color;
var filter = context.filter = "blur(" + 50 + "px)";
var dr = radius / period;
function draw() {
context.clearRect(0, 0, width, height);
let i = 0;
let deleted = 0;
let dt = -t + (t = window.performance.now());
context.beginPath();
while (i++ < points.length-1) {
let p = points[i];
p[2] += dt;
let r = radius - p[2] * dr;
context.moveTo(p[0], p[1]);
if (p[2] <= period) {
context.arc(p[0], p[1], r, 0, 2*Math.PI, true);
} else deleted = i;
}
context.fill();
points.splice(0, deleted);
context.beginPath();
context.arc(cursor[0], cursor[1], 20, 0, 2*Math.PI, true);
context.filter = "none";
context.fill();
context.filter = filter;
window.requestAnimationFrame(draw);
}
window.onmousemove = function(event) {
let x = event.pageX;
let y = event.pageY;
let backwardX = 0;
let backwardY = 0;
backwardX += (x-backwardX) / 5
backwardY += (y-backwardY) / 5
points.push([x, y, 0]);
cursor = [x, y];
}
t = window.performance.now();
window.requestAnimationFrame(draw);
body {
height: 100%;
width: 100%;
position: absolute;
cursor: none;
margin: 0;
}
<canvas id="canvas"></canvas>
PS: Question in Russian.
It's slow because you have a lot of overdraw. Each frame, a large number of points is being drawn, and each point touches a lot of pixels.
You can achieve something that looks very similar if you realize that the canvas retains its contents between frames. So every frame, you could do something like this:
Fade the canvas towards white by drawing a nearly transparent white rectangle over it.
Draw one new blurred point, at the current cursor location.
The circle that follows the mouse can easily be achieved by overlaying a separate element on top of the canvas, for example a <div>. Use transform: translate(x, y); to move it, which is more performant than using left/top because it's a compositor-only property. Add will-change: transform; for an extra potential performance boost.
I'm doing a little game on JavaScript and have a problem, Example: I have my square move randomly and one red square always standing (not move), when my square touch the red square, it will automacally find the way avoid the red square, but i don't know how to make my square find its way through the red square. I'm using canvas withmethod canvas.drawRect.
imgur.com/a/7e93Xn6, Here is my square, i want it move up or down automacaly to avoid the red square, but don't know how to make it, Thanks
This is actually not too complicated you just have to continually check if those two squares will touch if one of them is moving. This can be calculated using it's screen positions and size. If those are about to collide move sideways as long as moving forward would still lead into the other square.
Here's a quick example:
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
var context = canvas.getContext("2d");
var Squares = function(xPos, yPos, wid, hei, col) {
this.x = xPos;
this.y = yPos;
this.width = wid;
this.height = hei;
this.color = col;
this.speed = 0;
}
var redSquare = new Squares(200, 100, 40, 40, "#ff0000");
var blueSquare = new Squares(0, 100, 40, 40, "#0000ff");
blueSquare.speed = 3;
var squares = [redSquare, blueSquare];
function loop() {
if (blueSquare.x + blueSquare.width + blueSquare.speed > redSquare.x && blueSquare.y + blueSquare.height > redSquare.y) {
blueSquare.x = redSquare.x - blueSquare.width;
blueSquare.y -= blueSquare.speed;
} else {
blueSquare.x += blueSquare.speed;
}
if (blueSquare.x > canvas.width) {
blueSquare.x = 0;
blueSquare.y = 100;
}
context.clearRect(0, 0, canvas.width, canvas.height);
for (var a = 0; a < squares.length; a++) {
context.fillStyle = squares[a].color;
context.fillRect(squares[a].x, squares[a].y, squares[a].width, squares[a].height);
}
}
var interval = setInterval(loop, 20);
I would like to make the text in a canvas to appears and disappers from left to right. I don't know if it's possible. I tried to hide the text with the clip() function, but I can't transform the clip to reduce the width and make the text appears.
ctx.fillStyle = "#1f2f90";
ctx.fillText('This is a text!', 150, 100);
ctx.rect(50, 20, 200, 120);
ctx.stroke();
ctx.clip();
var i = 200;
setInterval(function(){
ctx.clearRect(0,0,300,150);
ctx.rect(50,20,i,120);
ctx.stroke();
ctx.clip();
i++;
},20);
Here is an example using fillRect to cover the text.
let ctx = document.getElementById('canvas').getContext('2d');
ctx.fillStyle = "#1f2f90";
ctx.fillText('This is a text!', 150, 100);
var i = 0;
setInterval(function(){
ctx.fillStyle = "white";
ctx.fillRect(50,20,i,120);
ctx.strokeRect(50, 20, 200, 120);
i++;
},20);
<canvas id="canvas"></canvas>
My solution is different. Every letter is an object with a position and a transparency alpha. During the animation the transparency of of the letters is decreasing, one letter at a time.
You may restart the animation on click.
Please read the comments in the code.
const canvas = document.getElementById("canvas");
const _canvas = document.getElementById("_canvas");
const ctx = canvas.getContext("2d");
const _ctx = _canvas.getContext("2d");
let cw = canvas.width = _canvas.width = 400,
cx = cw / 2;
let ch = canvas.height = _canvas.height = 150,
cy = ch / 2;
let rid = null;// request animation id
let theText = 'This is a text!';
let letters = [];// the letters array
let k = 20;// controls the speed of the animation
ctx.font = _ctx.font = "2em Verdana";
// every letter is an object
class Letter{
constructor(char,x){
this.char = char;// the letter
// measure the letter
_ctx.fillText(this.char, 0, cy);
this.w = _ctx.measureText(this.char).width;
//the position of the text
this.pos = {}
this.pos.y = cy;
this.pos.x = x;
// the transparency of the letter
this.alpha = 1;
}
show(){// draw the letter
ctx.fillStyle = `rgba(0,0,0,${this.alpha})`;
ctx.fillText(this.char, this.pos.x, this.pos.y);
}
update(){
//change the transparency of the text
if(this.alpha > 0){this.alpha -= 1/k;}
if(this.alpha < 0){this.alpha = 0; index++}
}
}
let x = 0;
for(l=0; l<theText.length; l++){
// a new letter object
letters.push(new Letter(theText[l],x))
//calculate the x position of the next letter
x = letters.reduce( function(a, b){return a + b.w}, 0);
}
// draw all the letters
for(l=0; l<letters.length; l++){letters[l].show()}
// the actual letter.
let index = 0;
function Draw() {
rid = window.requestAnimationFrame(Draw);
ctx.clearRect(0,0,cw,ch);
letters[index].update();//change the transparency of the actual letter
// draw all the letters
for(l=0; l<letters.length; l++){letters[l].show()}
// if the last letter is fully transparent stop the animation
if(letters[letters.length - 1].alpha <= 0){
window.cancelAnimationFrame(rid);rid = null;
}
}
Draw();
//resume animation on click
canvas.addEventListener("click",()=>{
if(rid){window.cancelAnimationFrame(rid);rid = null;}
index = 0;
for(l=0; l<letters.length; l++){letters[l].alpha = 1;letters[l].show()}
Draw();
})
#_canvas{display:none;}
canvas {
border:1px solid;
}
<canvas id="canvas"></canvas>
<canvas id="_canvas"></canvas>
the rotate() function seems to rotate the whole drawing area. Is there a way to rotate paths individually? I want the center for the rotation to be the object, not the drawing area.
Using save() and restore() still makes rotate take into account the whole drawing area.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.save();
context.fillStyle = 'red';
context.rotate(0.35);
context.fillRect(40,40, 100, 100);
context.restore();
context.save();
context.fillStyle = 'blue';
context.rotate(0.35);
context.fillRect(200, 40, 100, 100);
context.restore();
<canvas id="canvas" width="500" height="500"></canvas>
Use local space
Instead of drawing object at the position you want them draw everything around its own origin in its local space. The origin is at (0,0) and is the location that the object rotates around.
So if you have a rectangle that you draw with
function drawRect(){
context.fillRect(200, 40, 100, 100);
}
change it so that it is drawn at its origin
function drawRect(){
context.fillRect(-50,-50 , 100, 100);
}
Now you can easily draw it wherevery you want
Start with the setTransform function as that clears any existing tranforms and is a convenient way to set the location of the center of the object will be
ctx.setTransform(1,0,0,1,posX,posY); // clear transform and set center location
if you want to rotate it then add the rotation
ctx.rotate(ang);
and scale with
ctx.scale(scale,scale);
if you have two different scales you should scale before the rotate.
Now just call the draw function
drawRect();
and it is drawn with its center at posX,posY rotated and scaled.
You can combine it all into a function that has the x,y position, the width and the height, scale and rotation. You can include the scale in the setTransform
function drawRect(x,y,w,h,scale,rotation){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.strokeRect(-w/2,-h/2,w,h);
}
It also applies to an image as a sprite, and I will include a alpha
function drawImage(img,x,y,w,h,scale,rotation,alpha){
ctx.globalAlpha = alpha;
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.drawImage(img,-img.width/2,-img.height/2,img.width,img.height);
}
On a 6 year old laptop that can draw 2000 sprites on firefox every 1/60th of a second, each rotated, scaled, positioned, and with a alpha fade.
No need to mess about with translating back and forward. Just keep all the objects you draw around there own origins and move that origin via the transform.
Update
Lost the demo so here it is to show how to do it in practice.
Just draws a lot of rotated, scaled translated, alphaed rectangles.
By using setTransform you save a lot of time by avoiding save and restore
// create canvas and add resize
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);
// simple function to draw a rectangle
var drawRect = function(x,y,w,h,scale,rot,alpha,col){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha;
ctx.strokeStyle = col;
ctx.strokeRect(-w/2,-h/2, w, h);
}
// create some rectangles in unit scale so that they can be scaled to fit
// what ever screen size this is in
var rects = [];
for(var i = 0; i < 200; i ++){
rects[i] = {
x : Math.random(),
y : Math.random(),
w : Math.random() * 0.1,
h : Math.random() * 0.1,
scale : 1,
rotate : 0,
dr : (Math.random() - 0.5)*0.1, // rotation rate
ds : Math.random()*0.01, // scale vary rate
da : Math.random()*0.01, // alpha vary rate
col : "hsl("+Math.floor(Math.random()*360)+",100%,50%)",
};
}
// draw everything once a frame
function update(time){
var w,h;
w = canvas.width; // get canvas size incase there has been a resize
h = canvas.height;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,h); // clear the canvas
// update and draw each rect
for(var i = 0; i < rects.length; i ++){
var rec = rects[i];
rec.rotate += rec.dr;
drawRect(rec.x * w, rec.y * h, rec.w * w,rec.h * h,rec.scale + Math.sin(time * rec.ds) * 0.4,rec.rotate,Math.sin(time * rec.da) *0.5 + 0.5,rec.col);
}
requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);
All transformations in canvas are for the whole drawing area. If you want to rotate around a point you're going to have to translate that point to the origin, do your rotation and translate it back. Something like this is what you want.
Use a rotate function to rotate all of the shape's points around its center.
<!DOCTYPE html>
<html>
<head>
<style>
body
{
margin: 0px;
padding: 0px;
overflow: hidden;
}
canvas
{
position: absolute;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas;
var context;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var degreesToRadians = function(degrees)
{
return degrees*Math.PI/180;
}
var rotate = function(x, y, cx, cy, degrees)
{
var radians = degreesToRadians(degrees);
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return new Vector2(nx, ny);
}
var Vector2 = function(x, y)
{
return {x:x,y:y};
}
var Shape = function(points, color)
{
this.color = color;
this.points = points;
};
Shape.prototype.rotate = function(degrees)
{
var center = this.getCenter();
for (var i = 0; i < this.points.length; i++)
{
this.points[i] = rotate(this.points[i].x,this.points[i].y,center.x,center.y,degrees);
}
context.beginPath();
context.arc(center.x,center.y,35,0,Math.PI*2);
context.closePath();
context.stroke();
}
Shape.prototype.draw = function()
{
context.fillStyle = this.color;
context.strokeStyle = "#000000";
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for (var i = 0; i < this.points.length; i++)
{
context.lineTo(this.points[i].x, this.points[i].y);
//context.fillText(i+1, this.points[i].x, this.points[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
Shape.prototype.getCenter = function()
{
var center = {x:0,y:0};
for (var i = 0; i < this.points.length; i++)
{
center.x += this.points[i].x;
center.y += this.points[i].y;
}
center.x /= this.points.length;
center.y /= this.points.length;
return center;
}
Shape.prototype.translate = function(x, y)
{
for (var i = 0; i < this.points.length; i++)
{
this.points[i].x += x;
this.points[i].y += y;
}
}
var Rect = function(x,y,w,h,c)
{
this.color = c;
this.points = [Vector2(x,y),Vector2(x+w,y),Vector2(x+w,y+h),Vector2(x,y+h)];
}
Rect.prototype = Shape.prototype;
var r = new Rect(50, 50, 200, 100, "#ff0000");
r.draw();
r.translate(300,0);
r.rotate(30);
r.draw();
</script>
</body>
</html>
I'm fairly new to Javascript so I was hoping for a bit of help. I've been playing with animating a graph using canvas and javascript. I have it so that when the page loads, it will fill up the graph to a pre-determined percentage.
I also have some buttons that have an onclick on them, and the aim is for clicking these buttons to execute the animating function again but with a different percentage. However, clicking on them doesn't so a thing.
Any help you could give me on this would be great.
Code:
window.onload = function(){
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = 0;
var new_degrees = 0;
var difference = 0;
var color = "#c70505"; //green looks better to me
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init()
{
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W/2, H/2, 100, 0, Math.PI*2, false); //you can see the arc now
ctx.stroke();
//gauge will be a simple arc
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//The arc starts from the rightmost end. If we deduct 90 degrees from the angles
//the arc will start from the topmost end
ctx.arc(W/2, H/2, 100, 0 - 90*Math.PI/180, radians - 90*Math.PI/180, false);
//you can see the arc now
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees/360*100) + "%";
//Lets center the text
//deducting half of text width from position x
text_width = ctx.measureText(text).width;
//adding manual value to position y since the height of the text cannot
//be measured easily. There are hacks but we will keep it manual for now.
ctx.fillText(text, W/2 - text_width/2, H/2 + 15);
}
function draw(percent)
{
//Cancel any movement animation if a new chart is requested
if(typeof animation_loop != undefined) clearInterval(animation_loop);
//random degree from 0 to 360
new_degrees = percent
difference = new_degrees - degrees;
//This will animate the gauge to new positions
//The animation will take 1 second
//time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000/difference);
}
//function to make the chart move to new degrees
function animate_to()
{
//clear animation loop if degrees reaches to new_degrees
if(degrees == new_degrees)
clearInterval(animation_loop);
if(degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw(100);
}
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="draw(360)">100%</button>
<button onclick="draw(270)">75%</button>
The draw function is out of scope as soon as the anonymous function you have assigned to window.onload finishes executing. If you declare the functions in the global scope instead, you'll be able to call them anywhere. Here's a working fiddle.
Try my update:
<script>
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = 0;
var new_degrees = 0;
var difference = 0;
var color = "#c70505"; //green looks better to me
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init()
{
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W/2, H/2, 100, 0, Math.PI*2, false); //you can see the arc now
ctx.stroke();
//gauge will be a simple arc
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//The arc starts from the rightmost end. If we deduct 90 degrees from the angles
//the arc will start from the topmost end
ctx.arc(W/2, H/2, 100, 0 - 90*Math.PI/180, radians - 90*Math.PI/180, false);
//you can see the arc now
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees/360*100) + "%";
//Lets center the text
//deducting half of text width from position x
text_width = ctx.measureText(text).width;
//adding manual value to position y since the height of the text cannot
//be measured easily. There are hacks but we will keep it manual for now.
ctx.fillText(text, W/2 - text_width/2, H/2 + 15);
}
function draw(percent)
{
//Cancel any movement animation if a new chart is requested
if(typeof animation_loop != undefined) clearInterval(animation_loop);
//random degree from 0 to 360
new_degrees = percent
difference = new_degrees - degrees;
//This will animate the gauge to new positions
//The animation will take 1 second
//time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000/difference);
}
//function to make the chart move to new degrees
function animate_to()
{
//clear animation loop if degrees reaches to new_degrees
if(degrees == new_degrees)
clearInterval(animation_loop);
if(degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw(100);
</script>
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="draw(360)">100%</button>
<button onclick="draw(270)">75%</button>