By default the rectangle of my canvas should move from left to right. When pressing the key down, it should start moving down.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0;
function draw(x_move, y_move) {
requestAnimationFrame(function() {
draw(x_move, y_move);
});
ctx.beginPath();
ctx.rect(x, y, 20, 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
x = x + x_move;
y = y + y_move;
}
draw(1, 0);
document.addEventListener('keydown', function(event) {
//console.log(event.keyCode);
if (event.keyCode == 40) {
draw(0, 1);
}
});
canvas { background-color: red; }
<canvas id="canvas" width="600" height="400"></canvas>
Using the code above, you may at least notice two issues:
When pressing key down, the rectangle moves down but also keeps moving to the right.
When you keep pressing the down key multiple times, the speed gets increased.
How can I fix those issues?
See where you call
requestAnimationFrame(function() {
draw(x_move, y_move);
});
You're now recursively calling draw().
Now every time you call draw(), it gets called over and over and over again. And when you call it from the keydown event, you call it 2x as often, then 3x as often on the second time you press it, etc.
I'm not entirely clear on what your objective is here, but if you wanted to just adjust the trajectory of the square by pressing down, you could do something like this:
document.addEventListener('keydown', function(event) {
//console.log(event.keyCode);
if (event.keyCode == 40) {
adjustDownwardTrajectory();
}
});
and have that adjustDownwardTrajectory() change some downwardTrajectory variable such that:
function draw(x_move, y_move) {
requestAnimationFrame(function() {
draw(x_move, y_move);
});
ctx.beginPath();
ctx.rect(x, y, 20, 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
x = x + x_move;
y = y + y_move + downwardTrajectory;
}
I believe this is what you want to be doing (and you were close!). I hope this is the behaviour you were looking for.
A related point: It may be a good idea to wrap x_move and y_move up in a closure with these functions so you avoid accidentally changing them in other parts of the program. That way the functions you implement for changing directions of the square will have private access to these variables.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0;
var x_move = 1;
var y_move = 0;
function draw() {
requestAnimationFrame(function() {
draw();
});
ctx.beginPath();
ctx.rect(x, y, 20, 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
x = x + x_move;
y = y + y_move;
}
draw();
document.addEventListener('keydown', function(event) {
//console.log(event.keyCode);
if (event.keyCode == 40) {
x_move = 0;
y_move = 1;
}
});
canvas {
background-color: red;
}
<canvas id="canvas" width="600" height="400"></canvas>
keyboardEvent.keyCode is a depreciated property. You should use keyboardEvent.code or keyboardEvent.key they provided a named key which is much easier to manage than trying to remember key codes.
You can use fillRect rather than rect and you don't need to use beginPath and fill. You don't need to use closePath you only use it if you want to close a shape (ie add a line from the last point to the first) rect is already closed
You can set the callback directly as the argument for requestAnimationFrame
It pays to group related data together. In this case the square has a position x, y and a delta position (x_move, y_move) that are better off in a object. A good clue that you have related data is when you find yourself adding the same name to a set of variables. Objects can also include behaviour in the form of functions like draw and update
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// list of named keys to record state of
const keys = {
ArrowDown: false,
};
// create key event listeners
document.addEventListener('keydown', keyEvent);
document.addEventListener('keyup', keyEvent);
// define the square
const square = {
x : 0, // position
y : 0,
dx : 0, // delta pos
dy : 0,
draw () { // function to render the square
ctx.fillStyle = "#ffffff";
ctx.fillRect(this.x, this.y, 20, 20);
},
update () { // function to update square
if(keys.ArrowDown){
this.dx = 0;
this.dy = 1;
}else {
this.dx = 1;
this.dy = 0;
}
this.x += this.dx;
this.y += this.dy;
}
}
// main animation update function called once per frame 1/60th second
function update () {
requestAnimationFrame(update);
ctx.clearRect(0, 0, canvas.width, canvas.height);
square.update();
square.draw();
}
// key event records named key state (false up, true down)
function keyEvent (event) {
if(keys[event.code] !== undefined){
keys[event.code] = event.type === "keydown";
}
}
Related
I'm doing a Pong game in javascript in order to learn making games, and I want to make it object oriented.
I can't get clearRect to work. All it does is draw a line that grows longer.
Here is the relevant code:
function Ball(){
this.radius = 5;
this.Y = 20;
this.X = 25;
this.draw = function() {
ctx.arc(this.X, this.Y, this.radius, 0, Math.PI*2, true);
ctx.fillStyle = '#00ff00';
ctx.fill();
};
}
var ball = new Ball();
function draw(){
player.draw();
ball.draw();
}
function update(){
ctx.clearRect(0, 0, 800, 400);
draw();
ball.X++;
}
I've tried to put the ctx.clearRect part in the draw() and ball.draw() functions and it doesn't work.
I also tried fillRect with white but it gives the same results.
How can I fix this?
Your real problem is you are not closing your circle's path.
Add ctx.beginPath() before you draw the circle.
jsFiddle.
Also, some tips...
Your assets should not be responsible for drawing themselves (their draw() method). Instead, perhaps define their visual properties (is it a circle? radius?) and let your main render function handle canvas specific drawing (this also has the advantage that you can switch your renderer to regular DOM elements or WebGL further down the track easily).
Instead of setInterval(), use requestAnimationFrame(). Support is not that great at the moment so you may want to shim its functionality with setInterval() or the recursive setTimeout() pattern.
Your clearRect() should be passed the dimensions from the canvas element (or have them defined somewhere). Including them in your rendering functions is akin to magic numbers and could lead to a maintenance issue further down the track.
window.onload = function() {
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
var cvsW = cvs.Width;
var cvsH = cvs.Height;
var snakeW = 10;
var snakeH = 10;
function drawSnake(x, y) {
ctx.fillStyle = '#FFF';
ctx.fillRect(x*snakeW, y * snakeH, snakeW, snakeH);
ctx.fillStyle = '#000';
ctx.strokeRect(x*snakeW, y * snakeH, snakeW, snakeH);
}
// drawSnake(4, 5)
//create our snake object, it will contain 4 cells in default
var len = 4;
var snake = [];
for(var i = len -1; i >=0; i--) {
snake.push(
{
x: i,
y: 0
}
)
};
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH)
for(var i = 0; i < snake.length; i++) {
var x = snake[i].x;
var y = snake[i].y;
drawSnake(x, y)
}
//snake head
var snakeX = snake[0].x;
var snakeY = snake[0].y;
//remove to last entry (the snake Tail);
snake.pop();
// //create a new head, based on the previous head and the direction;
snakeX++;
let newHead = {
x: snakeX,
y: snakeY
}
snake.unshift(newHead)
}
setInterval(draw, 60);
}
my clearRect is not working, what's the problem and solution?
I want to be able to hover my mouse over different rectangles and have the rectangle change color when hovered, what I have now works for the last rectangle but the others get cleared. The rectangles are created using a class/constructor, an array, and a loop. Code is below:
/*Variables*/
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d'),
square;
/*Board Class*/
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
}
drawBoard() {
let canvasWidth = window.innerWidth * .95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
square = new Path2D();
ctx.rotate(this.angle * Math.PI / 180);
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(square);
}
}
/*Event Listener for changing rectangle color and redrawing*/
canvas.addEventListener('mousemove', function(event) {
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
});
/*Instantiate Array*/
let b = [];
/*Loop to create boards and push to array*/
for(let i = 1; i < 11; i++){
b.push(new Board(.05 * i, .25, .04, .03, 0));
}
/*Function to loop through array and draw boards when page loads*/
function loadFunctions(){
background.draw();
b.forEach(function(board){
board.drawBoard();
})
}
This is my first project with the Canvas API and it's giving me a lot of trouble, normally I could identify the shape by class/id if it where made with a regular HTML element but I'm not sure where to go from here...
I've tried looping through the array that contains the board info but cannot get anything to work. Any help is appreciated!
Thanks
Let's step through your code, to get a better picture of what's going on.
As soon as you move your mouse over the canvas, the mousemove listener gets fired and executes it's associated callback function.
Inside this callback function we'll find this as the very first line:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY))
So this if-statement checks it the current mouse position is inside of square. Well, the big question is: what is square actually?
If we look over your code a bit more, we'll find out that it's a global variable, which gets some value inside the Board class drawBoard() function as:
square = new Path2D();
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
Apparently it's a Path2D holding the rectangle of one of the bars - but which one actually?
Let's take a look at this function:
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
and
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
In the first loop, you're populating the array b with ten instances of Board and in the forEach loop, you're calling each Board's drawBoard() function.
What does all this mean? Yes, square will always hold a reference to the bar, which's drawBoard() function has been called the last time - which will always be the last Board in your array.
To summarize: the only bar your checking in the mousemove callback is always the last one in the array.
So:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
translated to plain english means: if the point is in square's bound, set the fillStyle to red, clear the whole screen and afterwards fill one bar with red.
What you need to do instead is checking the mouse position with every Board instance from the array. It ain't to hard though - just make the Path2D a class variable of Board and inside the callback function loop over the whole array and compare the mouse position with each Board's .square property.
Here's an example (just click on 'Run code snippet'):
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d');
let b = [];
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
this.square = new Path2D();
}
drawBoard() {
let canvasWidth = window.innerWidth * 0.95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
ctx.rotate(this.angle * Math.PI / 180);
this.square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(this.square);
}
}
canvas.addEventListener('mousemove', function(event) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentSquare;
for (let i = 0; i < b.length; i++) {
currentSquare = b[i].square;
if (ctx.isPointInPath(currentSquare, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
} else {
ctx.fillStyle = 'red';
}
ctx.fill(currentSquare);
}
});
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
loadFunctions();
<canvas id="canvas" width=500 height=300></canvas>
I'm trying to loop my animation, but no matter what I do, it won't loop. I'm pretty new to canvas, javascript and code in general.
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
var background = new Image();
background.src =
"C:/Users/dylan/Desktop/ProjectTwo/Images/fabricationbackground.jpg";
background.onload = function(){
}
//Loading all of my canvas
var posi =[];
posi[1] = 20;
posi[2] = 20;
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi =xy(posi[1],posi[2])
}
function xy(x,y){
ctx.drawImage(background,0,0);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#FFFFFFF";
ctx.fill();
ctx.closePath();
var newpos=[];
newpos[1]= x +dx;
newpos[2]= y +dy;
return newpos;
//Drawing the ball, making it move off canvas.
if (newpos[1] > canvas.width) {
newpos[1] = 20;
}
if (newpos[2] > canvas.height) {
newpos[2] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
}
setInterval(drawballleft, 20);
//Looping the function
Please let me know if I've done something wrong, I really want to learn what I'm doing here. The ball is supposed to go off the canvas, and loop back onto itself, but it goes off the canvas and ends.
Thanks in advance!
I have made a few changes to your code.
First I am using requestAnimationFrame instead of setInterval. http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
Second I am not using an image because I didn't want to run into a CORS issue. But you can put your background image back.
I simplified your posi array to use indexes 0 and 1 instead of 1 and 2 to clean up how you create your array.
I moved your return from before the two ifs to after so the ball will move back to the left or top when it goes off the side. I think that was the real problem you were seeing
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
//Loading all of my canvas
var posi =[20,20];
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi = xy(posi[0],posi[1])
requestAnimationFrame(drawballleft);
}
function xy(x,y){
ctx.fillStyle = '#FFF';
ctx.fillRect(0,0,400,300);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
var newpos=[x+dx,y+dy];
//Drawing the ball, making it move off canvas.
if (newpos[0] > canvas.width) {
newpos[0] = 20;
}
if (newpos[1] > canvas.height) {
newpos[1] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
return newpos;
}
requestAnimationFrame(drawballleft);
canvas {
outline: 1px solid red;
}
<canvas width="400" height="300" id="fabrication"></canvas>
To make it all even simpler...
Use an external script for handling the canvas.
A really good one ;) :
https://github.com/GustavGenberg/handy-front-end#canvasjs
Include it with
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
Then it's this simple:
// Setup canvas
const canvas = new Canvas('my-canvas', 400, 300).start(function (ctx, handyObject, now) {
// init
handyObject.Ball = {};
handyObject.Ball.position = { x: 20, y: 20 };
handyObject.Ball.dx = 10;
handyObject.Ball.dy = 10;
handyObject.Ball.ballRadius = 4;
});
// Update loop, runs before draw loop
canvas.on('update', function (handyObject, delta, now) {
handyObject.Ball.position.x += handyObject.Ball.dx;
handyObject.Ball.position.y += handyObject.Ball.dy;
if(handyObject.Ball.position.x > canvas.width)
handyObject.Ball.position.x = 20;
if(handyObject.Ball.position.y > canvas.height)
handyObject.Ball.position.y = 20;
});
// Draw loop
canvas.on('draw', function (ctx, handyObject, delta, now) {
ctx.clear();
ctx.beginPath();
ctx.arc(handyObject.Ball.position.x, handyObject.Ball.position.y, handyObject.Ball.ballRadius, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
});
I restructured your code and used the external script, and now it looks much cleaner and easier to read and toubleshoot!
JSFiddle: https://jsfiddle.net/n7osvt7y/
I have some code below for the start of a snake game that I'm making using HTML5 canvas. For some reason, the red circle that I'm temporarily using to represent my snake is drawing constantly following the path the mouse moves in. and it uses the food as a starting point. Check it out in your browser, because it's really hard to describe. All I want is for the circle to follow the mouse and leave a small trail that ends and doesn't stay on the canvas. How would I go about doing this. Thanks in advance!
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Snake 2.0</title>
</head>
<style>
</style>
<body>
<div>
<canvas id="canvas" width=500 height=500></canvas>
</div>
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
makeFood();
function makeFood() {
foods = [];
for (var i = 0; i < 1; i++){
foods.push(new Food());
}
}
function Food() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 10;
}
function drawFood() {
for (var i = 0; i < 1; i++){
foods.push(new Food());
}
for (var i = 0; i < foods.length; i++){
var f = foods[i];
context.beginPath();
var grd = context.createRadialGradient(f.x, f.y, (f.radius - (f.radius - 1)), f.x + 1, f.y + 1, (f.radius));
grd.addColorStop(0, 'red');
grd.addColorStop(1, 'blue');
context.arc(f.x, f.y, f.radius, 0, 2 * Math.PI, true);
context.fillStyle = grd;
context.fill();
}
}
function makePower() {
powers = [];
for (var i = 0; i < 1; i++){
powers.push(new Power());
}
}
function Power() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 8;
}
function drawPower() {
for (var i = 0; i < powers.length; i++){
var p = powers[i];
context.beginPath();
var grd = context.createRadialGradient(p.x, p.y, (p.radius - (p.radius - 1)), p.x + 1, p.y + 1, (p.radius));
grd.addColorStop(0, 'green');
grd.addColorStop(1, 'yellow');
context.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
context.fillStyle = grd;
context.fill();
}
}
canvas.addEventListener("mousemove", function(event) {
move(event);
});
function move(e) {
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var a = e.clientX;
var b = e.clientY;
context.arc(a, b, 20, 0, 2 * Math.PI, true);
context.fillStyle = "red";
context.fill();
}
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var functions = [drawFood];
var timer = setInterval(function(){
drawFood();
}, 5000);
function stop() {
clearInterval(timer);
}
canvas.addEventListener("click", stop);
//timer = setInterval(start, 1000);
//timer = setInterval(start, 5000);
</script>
</body>
</html>
You could start by adding "context.beginPath();" in your "move" function, before "context.arc(a, b, 20, 0, 2 * Math.PI, true);", line 102-103 in my editor.
function move(e) {
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var a = e.clientX;
var b = e.clientY;
context.beginPath();
context.arc(a, b, 20, 0, 2 * Math.PI, true);
context.fillStyle = "red";
context.fill();
}
Here is the fiddle : http://jsfiddle.net/sd5hh57b/1/
You should store the positions you move along in an array. Then a new timer should revisit those discs and redraw them in a more faded color each time it ticks, until a disc becomes black. Then it should be removed from that array.
Here is fiddle that does that.
The change in the code starts at canvas.addEventListener("mousemove",... and goes like this:
canvas.addEventListener("mousemove", function(event) {
// Replaced move function by drawDisc function,
// which needs coordinates and color intensity
drawDisc(event.clientX, event.clientY, 0xF);
});
// Array to keep track of previous positions, i.e. the trail
var trail = [];
function drawDisc(x, y, red) {
context.beginPath();
context.arc(x, y, 20, 0, 2 * Math.PI, true);
context.fillStyle = '#' + red.toString(16) + '00000';
context.fill();
// If disc is not completely faded out, push it in the trail list
if (red) {
trail.push({x: x, y: y, red: red});
}
}
// New function to regularly redraw the trail
function fadeTrail() {
var discs = trail.length;
// If there is only one disc in the trail, leave it as-is,
// it represents the current position.
if (discs > 1) {
for (var i = discs; i; i--) {
// take "oldest" disc out of the array:
disc = trail.shift();
// and draw it with a more faded color, unless it is
// the current disc, which keeps its color
drawDisc(disc.x, disc.y, disc.red - (i === 1 ? 0 : 1));
}
}
}
// New timer to fade the trail
var timerFade = setInterval(function(){
fadeTrail();
}, 10);
I think the comments will make clear what this does. Note that the colors of the discs go from 0xF00000 to 0xE00000, 0xD00000, ... , 0x000000. Except the current disc, that one keeps its 0xF00000 color all the time.
The other answers are right :
Use beginPath() at each new arc() to create a new Path and avoid context.fill() considers the whole as a single Path.
Use a trail Array to store your last positions to draw the trail.
But, the use of setTimeout and setInterval should be avoided (and even further the use of multiple ones).
Modern browsers do support requestAnimationFrame timing method, and for olders (basically IE9), you can find polyfills quite easily. It has a lot of advantages that I won't enumerate here, read the docs.
Here is a modified version of your code, which uses a requestAnimationFrame loop.
I also created two offscreen canvases to update your foods and powers, this way they won't disappear at each draw. Both will be painted in the draw function.
I changed the mousemove handler so it only updates the trail array, leaving the drawing part in the draw loop. At each call, it will set a moving flag that will let our draw function know that we are moving the mouse. Otherwise, it will start to remove old trail arcs from the Array.
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
// create other contexts (layer like) for your food and powers
var foodContext = canvas.cloneNode(true).getContext('2d');
var pwrContext = canvas.cloneNode(true).getContext('2d');
// a global to tell weither we are moving or not
var moving;
// a global to store our animation requests and to allow us to pause it
var raf;
// an array to store our trail position
var trail = [];
// here we can determine how much of the last position we'll keep at max (can then be updated if we ate some food)
var trailLength = 10;
// your array for the foods
var foods = [];
// a global to store the last time we drawn the food, no more setInterval
var lastDrawnFood = 0;
// start the game
draw();
function makeFood() {
foods.push(new Food());
}
function Food() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 10;
}
function drawFood() {
// clear the food Canvas (this could be done only if we ate some, avoiding the loop through all our foods at each call of this method)
foodContext.clearRect(0, 0, canvas.width, canvas.height);
foods.push(new Food());
for (var i = 0; i < foods.length; i++) {
var f = foods[i];
// draw on the food context
foodContext.beginPath();
foodContext.arc(f.x, f.y, f.radius, 0, 2 * Math.PI, true);
var foodGrd = foodContext.createRadialGradient(f.x, f.y, (f.radius - (f.radius - 1)), f.x + 1, f.y + 1, (f.radius));
foodGrd.addColorStop(0, 'red');
foodGrd.addColorStop(1, 'blue');
foodContext.fillStyle = foodGrd;
foodContext.fill();
}
}
// I'll let you update this one
function makePower() {
powers = [];
for (var i = 0; i < 1; i++) {
powers.push(new Power());
}
}
function Power() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 8;
}
function drawPower() {
pwrContext.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < powers.length; i++) {
var p = powers[i];
var pwrGrd = pwrContext.createRadialGradient(p.x, p.y, (p.radius - (p.radius - 1)), p.x + 1, p.y + 1, (p.radius));
pwrGrd.addColorStop(0, 'green');
pwrGrd.addColorStop(1, 'yellow');
pwrContext.beginPath();
pwrContext.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
pwrContext.fillStyle = pwrGrd;
pwrContext.fill();
}
}
// the event object is already passed, no need for an anonymous function here
canvas.addEventListener("mousemove", move);
function move(e) {
// we paused the game, don't update our position
if (!raf) return;
// update the snake
var a = e.clientX - canvas.offsetLeft;
var b = e.clientY - canvas.offsetTop;
trail.splice(0, 0, {
x: a,
y: b
});
// tell our draw function that we moved
moving = true;
}
function draw(time) {
// our food timer
if (time - lastDrawnFood > 5000) {
lastDrawnFood = time;
drawFood();
}
// clear the canvas
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
// draw the food
context.drawImage(foodContext.canvas, 0, 0);
// draw the power
context.drawImage(pwrContext.canvas, 0, 0);
//draw the snake
for (var i = 0; i < trail.length; i++) {
// decrease the opacity
opacity = 1 - (i / trail.length);
context.fillStyle = "rgba(255, 0,0," + opacity + ")";
// don't forget to create a new Path for each circle
context.beginPath();
context.arc(trail[i].x, trail[i].y, 20, 0, 2 * Math.PI, true);
context.fill();
}
// if we're not moving or if our trail is too long
if ((!moving || trail.length > trailLength) && trail.length > 1)
// remove the oldest trail circle
trail.pop();
// we're not moving anymore
moving = false;
// update the animation request
raf = requestAnimationFrame(draw);
}
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
function toggleStop() {
if (!raf) {
// restart the animation
raf = window.requestAnimationFrame(draw);
} else {
// cancel the next call
cancelAnimationFrame(raf);
raf = 0;
}
}
canvas.addEventListener("click", toggleStop);
html, body{margin:0;}
<canvas id="canvas" width=500 height=500></canvas>
On my canvas animation, I have a problem where my circles are getting drawn "without the metaphorical pen" lifting from the canvas.
I need a way to stop the function and just draw one circle than another one.
Here is my JSFiddle (Warning: uses 100% of one logical processor core/thread).
JavaScript:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var size = 19;
var size_two = 19;
function start(){
requestAnimationFrame(start);
size++;
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
}
function othercircle(){
requestAnimationFrame(othercircle);
size_two++;
context.arc(500, 300, size_two, 0, 3*Math.PI);
}
start();
othercircle();
The other answers are good, but I wanted to highlight why your unwanted line is appearing.
Your unwanted connecting line
The unwanted connecting line is created because you must call context.beginPath() before drawing each arc. If you don't call begainPath the browser assumes you want to connect the 2 circle paths.
context.beginPath()
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
context.beginPath();
context.arc(200, 200, size_two, 0, 3*Math.PI);
context.stroke();
Just a couple more notes
Your othercircle is drawing an arc that's 3 * PI. 2*PI is a complete circle and any value above 2*PI will not add to the circle.
If it was your intent to draw an expanding stroke-circle, then you should clear the canvas at the start of each animation loop (before drawing the expanded circles).
One requestAnimationFrame is enough. You can put both your circle and your othercircle code in one requestAnimationFrame.
Example code and a Demo: http://jsfiddle.net/m1erickson/62mFF/
var sizeCounter=19;
var maxSizeCounter=60;
var size = sizeCounter;
var maxSize=40;
var size_two = sizeCounter;
var maxSizeTwo=60;
function start(){
if(sizeCounter<maxSizeCounter){ requestAnimationFrame(start); }
// clear the canvas if you want strokes instead of filled circles
context.clearRect(0,0,canvas.width,canvas.height);
context.beginPath()
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
if(size<maxSize){ size++; }
context.beginPath();
context.arc(200, 200, size_two, 0, 3*Math.PI);
context.stroke();
if(size_two<maxSizeTwo){ size_two++; }
sizeCounter++;
}
start();
Then use a condition to stop the recursive call, or just omit size++ and size_two++ from your code.
Your functions don't have all the information for a complete circle, thus the line between the two circles. Below will make two circles without the line between them.
function start(){
requestAnimationFrame(start);
size++;
context.beginPath();
context.arc(95, 85, size, 0, 2*Math.PI);
context.closePath();
context.fill();
}
function othercircle(){
requestAnimationFrame(othercircle);
size_two++;
context.beginPath();
context.arc(500, 300, size_two, 0, 3*Math.PI);
context.closePath();
context.fill();
}
Here is an updated jsFiddle
http://jsfiddle.net/gamealchemist/4nQCa/5
I think it's simpler to go object in your case : define a Circle class that will update with time and draw itself :
/// Circle class.
// has update(dt) and draw as public method.
function Circle(x, y, initSize, endSize, duration, color) {
this.x = x;
this.y = y;
this.size = initSize;
this.endSize = endSize;
this.color = color;
this.speed = (endSize - initSize) / duration;
this.update = function (dt) {
if (this.speed == 0) return;
this.size += dt * this.speed;
if (this.size > this.endSize) {
this.size = this.endSize;
this.speed = 0;
}
}
this.draw = function () {
context.beginPath();
context.strokeStyle = this.color;
context.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
context.stroke();
}
}
then hold all your animated objects within an array
var scene=[];
And have the objects animated/drawn :
function animate() {
// keep alive
requestAnimationFrame(animate);
// handle time
var callTime = Date.now();
var dt = callTime - lastTime;
lastTime = callTime;
// clear screen
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.fillText('Click anywhere !',40, 80);
// draw
for (var i = 0; i < scene.length; i++) {
var thisObject = scene[i];
thisObject.draw();
}
// update
for (var i = 0; i < scene.length; i++) {
var thisObject = scene[i];
thisObject.update(dt);
}
}
var lastTime = Date.now();
animate();
With this scheme it's easy to add/remove objects. For instance to add circles on mouse click :
addEventListener('mousedown', function (e) {
var x = e.clientX,
y = e.clientY;
var color = 'hsl(' + Math.floor(Math.random() * 360) + ',80%, 85%)';
var newCircle = new Circle(x, y, 20, 100, 1000, color);
scene.push(newCircle);
});