How to stop snake from moving diagonally? - javascript

I'm working on a Snake game in JavaScript and I want the snake to move only vertically or horizontally but it keeps moving diagonally. For example, if I press up, it moves up, but then if I press right, it'll move diagonally rather than only to the right.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake(dx, dy) {
const head = {
x: snakeCoord[0].x + dx,
y: snakeCoord[0].y + dy
};
snakeCoord.unshift(head);
snakeCoord.pop();
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawSnake();
setTimeout(function() {
moveSnake(dx, dy)
}, 100);
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
if (snakeCoord[0].y - length_width !== snakeCoord[1].y) {
moveSnake(0, -length_width);
}
} else if (key == "ArrowDown") {
if (snakeCoord[0].y + length_width !== snakeCoord[1].y) {
moveSnake(0, length_width);
}
} else if (key == "ArrowLeft") {
if (snakeCoord[0].x - length_width !== snakeCoord[1].x) {
moveSnake(-length_width, 0);
}
} else if (key == "ArrowRight") {
if (snakeCoord[0].x + length_width !== snakeCoord[1].x) {
moveSnake(length_width, 0);
}
}
}
drawSnake();
document.addEventListener("keyup", keyPress);
<canvas width="500" height="500"></canvas>

On every keypress and then recursively you are setting new timeout setTimeout(function(){ moveSnake(dx,dy) }, 100);. You end up with growing number of controdicting moveSnake calls.
You should save timeout to a variable and clear it with clearTimeout() on keypress before calling moveSnake().

Rather than have the keyboard handler call a move method that starts its own timer loop, you should have a single update routine that updates everything for one frame of animation. You should also drive rendering as fast as possible using requestAnimationFrame and have each render request the next animation frame. (See example at the link provided.) If you want slower animation then you can ratchet a step-by-step update of the scene with a separate timer. (Trust me, some day you'll want high frame-rate animation, even in your step-by-step game.)
I was bored so I decided to implement some changes to your code.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
let snake = {
dir: {dx: -1, dy: 0},
nextDir: [], // buffered direction changes
speed: 5, // steps per second
ratchet: 0
};
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake() {
if (snake.nextDir[0]) {
// only change directions if it doesn't result in doubling back on yourself
if (snakeCoord[0].x + snake.nextDir[0].dx * length_width !== snakeCoord[1].x
&& snakeCoord[0].y + snake.nextDir[0].dy * length_width !== snakeCoord[1].y) {
snake.dir = snake.nextDir[0];
}
snake.nextDir.shift(1);
}
const head = {
x: snakeCoord[0].x + snake.dir.dx * length_width,
y: snakeCoord[0].y + snake.dir.dy * length_width
};
snakeCoord.unshift(head);
snakeCoord.pop();
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
setDirection(0,-1);
} else if (key == "ArrowDown") {
setDirection(0, 1);
} else if (key == "ArrowLeft") {
setDirection(-1, 0);
} else if (key == "ArrowRight") {
setDirection(1, 0);
}
e.preventDefault();
}
drawSnake();
let lastTime = new Date();
window.requestAnimationFrame(render);
function setDirection(dx, dy) {
snake.nextDir.push({dx, dy}); // overwrite any pending direction changes.
}
function update() {
let now = Date.now();
let elapsed = (now - lastTime) / 1000;
snake.ratchet += elapsed * snake.speed;
while (snake.ratchet >= 1) {
moveSnake();
snake.ratchet -= 1;
}
lastTime = now;
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
update();
drawSnake();
window.requestAnimationFrame(render);
}
document.addEventListener("keydown", keyPress);
* {
overflow: hidden
}
<canvas width="500" height="500"></canvas>
Made this a high frame rate render loop. Employed a ratchet mechanism to move the snake discretely every so often at some rate (see snake.speed). Added a property of the snake which is its direction. (see snake.dir). Buffered keystrokes of requested direction changes (see snake.nextDir) Simplified the logic of preventing the snake from doubling back on itself. Eat up one direction change per move step.
You still need to do the snake self-collision. (Assuming this is what you're up to, with a traditional snake game.)
Anyway, I hope this helps you, or someone else.

Related

How to rotate an image a particular direction in phaser 3 without affecting collision

I’ve got a snake game (worm instead) and the movement works, press right arrow you go right etc. If it helps with question the live link rn is https://leons-worm-game.netlify.app/
However, I want to change the direction the head is facing when you do so. So if you turn right the head faces right etc. I managed to do it with:
this.head.angle = x
However, now my collision is messed up and also the body (once it grows from eating) is positioned weirdly in relation to the head (I think the head is being messed up though, not the position of the body).
My face functions:
faceLeft: function () {
if (this.direction === directions.UP || this.direction === directions.DOWN) {
this.head.angle = 270;
this.heading = directions.LEFT;
}
},
faceRight: function () {
if (this.direction === directions.UP || this.direction === directions.DOWN) {
this.head.angle = 90;
this.heading = directions.RIGHT;
}
},
faceUp: function () {
if (this.direction === directions.LEFT || this.direction === directions.RIGHT) {
this.head.angle = 0;
this.heading = directions.UP;
}
},
faceDown: function () {
if (this.direction === directions.LEFT || this.direction === directions.RIGHT) {
this.head.angle = 180;
this.heading = directions.DOWN;
}
},
Worm class:
let Worm = new Phaser.Class({
initialize: function Worm(scene, x, y) {
this.headPosition = new Phaser.Geom.Point(x, y);
this.body = scene.add.group();
this.head = this.body.create(x * 20, y * 20, 'head');
this.head.setOrigin(0);
this.alive = true;
this.speed = 100;
this.moveTime = 0;
this.tail = new Phaser.Geom.Point(x, y);
this.heading = directions.RIGHT;
this.direction = directions.RIGHT;
},
Move function:
move: function (time) {
/**
* Based on the heading property we update headPosition.
*
* The Math.wrap call allows the worm to wrap around the screen when it goes of.
*/
switch (this.heading) {
case directions.LEFT:
this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x - 1, 0, 40);
break;
case directions.RIGHT:
this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x + 1, 0, 40);
break;
case directions.UP:
this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y - 1, 0, 30);
break;
case directions.DOWN:
this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y + 1, 0, 30);
break;
}
this.direction = this.heading;
// Update the body segments
Phaser.Actions.ShiftPosition(
this.body.getChildren(),
this.headPosition.x * 20,
this.headPosition.y * 20,
1,
this.tail
);
// Check to see if any of the body pieces have the same x/y as the head
// If they do, the head ran into the body so update snakes "alive" status
let hitBody = Phaser.Actions.GetFirst(
this.body.getChildren(),
{ x: this.head.x, y: this.head.y },
1
);
if (hitBody) {
game.sound.stopAll();
deadEffect.play();

Block in canvas dissapeared for one frame

I'm trying to build Snake from scratch using Javascript. But when I use the arrow keys to get it from 1 part of the canvas to the other part of the canvas it dissapears for 1 frame, how to resolve this? You can try it on: https://annedegraaff.nl/snake/
<canvas id="snake" width="400" height="400">
</html>
<script>
var canvas;
var canvasContext;
var ball1X = 12.5;
var ball1Y = 12.5;
window.onload = function() {
canvas = document.getElementById('snake');
canvasContext = canvas.getContext('2d');
var framesPerSecond = 60;
setInterval(function() {
draw();
move();
}, 1000/framesPerSecond);
}
function move() {
window.onkeydown = function(e) {
var key = e.keyCode ? e.keyCode : e.which;
if (ball1X < 12.5) {
ball1X += 395;
}else if (ball1X > 385) {
ball1X -= 395;
}
if (key == 39) {
ball1X += 10;
}else if (key == 37) {
ball1X -= 10;
}else if (key == 40) {
ball1Y += 10;
}else if (key == 38) {
ball1Y -= 10;
}
}
}
function draw() {
canvasContext.fillStyle = 'green';
canvasContext.fillRect(0,0,canvas.width,canvas.height);
canvasContext.fillStyle = 'black';
canvasContext.fillRect(ball1X,ball1Y,10,10);
}
</script>
Logic error in keyEvent handler
Though not directly evident where the problem is in the given code I am assuming it is the test for edges in the keydown handler. There are also other ting being done incorrectly that will present additional problems and difficulties as you develop the game.
Your bug
In your keyboard function you test if the ball is close to the edge and if so you move it to the other size. Looks like you move it too far and thus can not be seen.
The following is a quick fix. Move the test to after the ball has been moved and make sure the the move to the other side does not put it too far so that it is moved again on the next event.
window.onkeydown = function(e) {
var key = e.keyCode ? e.keyCode : e.which;
if (key == 39) {
ball1X += 10;
}else if (key == 37) {
ball1X -= 10;
}else if (key == 40) {
ball1Y += 10;
}else if (key == 38) {
ball1Y -= 10;
}
if (ball1X < 12.5) {
ball1X += 400;
}else if (ball1X > 400-12.5) {
ball1X -= 400;
}
}
Other problems.
Use requestAnimationFrame for animations not setInterval
Key event listeners should only record the keyboard state as they have nothing to do with the game and dont know what to do with the keys pressed. The game code should use the keyboard state and its own current state to work out what to do with each key
Use addEventListener add events as directly setting event can be overwritten
Encapsulate your game inside a function so that all the variables and functions are isolated from the global names space and you can easily insert the game into any page.
Use objects to group properties and function together. Eg you had ballX, ballY and most like will add other properties each will have a ball prefix. By creating an object named ball and adding properties like x,y you can get access to the balls x, y with via a reference ball.x, ball.y or var b = ball; b.x += 1;`. Once an object has been defined you can make many copies easily.
Change the key handler to hold the key state of only the keys you are interested in. You only want to know if the key is down so listen to key up and down setting a flag to true when a key is down.
And other stuff
Rewrite
A quick rewrite showing a better way to implement what you had. It is a recommendation only. It is a little longer than you had it and is not the only way, but if you write like this it will be easier as the game develops.
See comments for the reasons and what does what.
// create onload event handler as a function and encapsulate all variables and functions to key global name space clean
function start(){
// create canvas and context
const canvas = document.getElementById("snakeCanvas");
const ctx = canvas.getContext("2d"); // formaly canvasContext;
// get the size as we use that a lot
const width = canvas.width;
const height = canvas.height;
requestAnimationFrame(mainLoop); // will call mainloop after this function (start) has run
// create an object to hold all related properties and
// functions for the ball
const ball = { // position ball in center
x : width / 2 | 0, // the or zero ( | 0) rounds down to nearest integer
y : height / 2 | 0,
size : 10,
speed : 10,
draw() { // function to draw the ball
ctx.fillStyle = 'black';
ctx.fillRect(this.x - this.size / 2,this.y - this.size / 2, this.size, this.size);
},
update() { // moves the ball
if (keys.up === true) { this.y -= this.speed }
if (keys.down === true) { this.y += this.speed }
if (keys.left === true) { this.x -= this.speed }
if (keys.right === true) { this.x += this.speed }
// get half size
const hSize = this.size / 2;
// check for edges and move to other side of canvas
if(this.x + hSize < 0) { this.x += width }
if(this.x - hSize > width) { this.x -= width }
if(this.y + hSize < 0) { this.y += height }
if(this.y - hSize > height) { this.y -= height }
},
}
// the background function clears and displays the background
function background(){
ctx.fillStyle = 'green';
ctx.fillRect(0,0,width,height);
}
// Object to hold the current keyboard state
const keys = {
up : false,
down : false,
left : false,
right : false,
map : new Map([ // use a Map to find keys
[39,"right"], // key code and string name of keys.name
[37,"left"],
[40,"down"],
[38,"up"],
])
}
// the key event listener
function keyEvents(event){
// get key code as a string
const keyCode = event.keyCode ? event.keyCode : event.which;
// get if avalible the key name from the map
const key = keys.map.get(keyCode);
// if a key is mapped set its state
if(key){
keys[key] = event.type === "keydown";
event.preventDefault(); // pervent default action
}
}
// listent to the keyboard events and set the keyboard state
["keydown","keyup"].forEach(eventName => addEventListener(eventName, keyEvents));
// for the stackoverflow snippet we need to get focus to
// hear any of the key events
focus();
function mainLoop(time){ // time is automatic and in ms (1/1000th second)
background(); // call the background function that clears and displays the background
// update and draw the ball object
ball.update();
ball.draw();
requestAnimationFrame(mainLoop); // request next frame in 1/60th second
}
}
// when loaded start the game
addEventListener("load", start);
<canvas id="snakeCanvas" width="400" height="400">

Mouse position is only read on the first frame

i have been having trouble with reading a mouse position on a canvas. The code is working (semi) correctly as it reads the position when clicking he canvas in IE but only on one frame, in chrome it is just displaying the value as 0.
Here is the full code:
<script>
var blip = new Audio("blip.mp3");
blip.load();
var levelUp = new Audio("levelUp.mp3");
levelUp.load();
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
context.font = '18pt Calibri';
context.fillStyle = 'white';
//load and draw background image
var bgReady = false;
var background = new Image();
background.src = 'images/background.jpg';
background.onload = function(){
bgReady = true;
}
var startMessage = 'Click the canvas to start';
//load plane image
var planeReady = false;
var planeImage = new Image();
planeImage.src = 'images/plane.png';
planeImage.onload = function() {
planeReady = true;
}
//load missile image
var missileReady = false;
var missileImage = new Image();
missileImage.src = 'images/missile-flipped.gif';
missileImage.onload = function() {
missileReady = true;
}
//initialise lives and score
var score = 0;
var lives = 3;
var missilesLaunched = 0;
var missileSpeed = 5;
var level = 1;
var missileX = 960;
var missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
function getMousePos(canvas, event) {
return {
x: input.x - rect.left,
y: input.y - rect.top
};
}
function update_images(event) {
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
planeImage.y = pos.y;
missileX = missileX - missileSpeed;
if (missileX < - 70) {
missilesLaunched++;
missileX = 960;
missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
blip.play();
score = missilesLaunched;
if (score % 5 == 0) {
missileSpeed = missileSpeed + 2;
level++;
levelUp.play();
}
}
}
function reload_images() {
if (bgReady = true) {
context.drawImage(background, 0, 0);
}
if (planeReady = true) {
context.drawImage(planeImage, 10, planeImage.y);
}
if (missileReady = true) {
context.drawImage(missileImage, missileX, missileY);
}
context.fillText('Lives: ' + lives, 200, 30);
context.fillText('Score: ' + score, 650, 30);
context.fillText('Level: ' + missileSpeed, 420, 30);
context.fillText('Position: ' + missileImage.y, 420, 70);
}
function main(event) {
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
update_images(event);
reload_images();
if (lives > 0) {
window.requestAnimationFrame(main);
}
else {
}
}
function start() {
context.drawImage(background, 0, 0);
context.fillText('Click the canvas to start', 350, 250);
function startMain(event) {
game.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("mousedown", startMain);
}
start();
</script>
Joe, you should actually be capturing the mouse position every time you click...
...but you're actually also starting a new game (without stopping the old one), every time you click, too.
First problem: starting game engine several times to draw on the same instance of the canvas
Solution:
In your start function, you need to remove the mousedown event listener, after you've triggered it.
function start () {
// ... other setup
function startMain (event) {
canvas.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("click", startMain);
}
Now it will only listen for the first click, before starting, and will only start once.
Second Problem: mouse doesn't update as expected
Solution: two issues here...
...first, you are passing event into main on first call...
...after that, you're passing main into requestAnimationFrame.
requestAnimationFrame won't call it with an event, it will call it with the number of microseconds (or ms or some other unit as a fractional precision of ms) since the page was loaded.
So the first time you got main({ type: "mousedown", ... });.
The next time you get main(4378.002358007);
So lets refactor the startMain we had above, so that main never ever collects an event, just a time.
function startMain ( ) {
canvas.removeEventListener("click", startMain);
requestAnimationFrame(main);
}
The next problem is that even if you were getting just events, you're only ever capturing a click event (which as we mentioned earlier, fires a new copy of the game logic).
Your solution is to separate the code which catches mouse events from the code which reads mouse position.
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
function getMousePos (rect, input) {
return {
x : input.x - rect.left,
y : input.y - rect.top
};
}
// currently in updateImages (should not be there, but... a different story)
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
You've got other problems, too...
You're calling getMousePos and passing in game at the moment. I don't see where game is defined in your JS, so either you're making game somewhere else (begging for bugs), or it's undefined, and your app blows up right there.
You should really be building this with your console / dev-tools open, in a hands-on fashion, and cleaning bugs in each section, as you go.

Getting a gun to fire

I'm trying to make a top down shooter game to learn about coding in HTML/JS/CSS.
I've got a canvas, my player moves and rotates on the canvas, but I'm having some trouble getting his gun working. I've got this with the aid of tutorials and searching through other stackoverflow posts, but this one I can't seem to fix alone.
I have defined a variable 'gunfire' which is set to 1 if the left mouse button is pressed and is otherwise 0, in my function draw() I have an if statement that should draw a rectangle in front of my sprite (to represent bullets) when the left mouse button is pressed.
The problem I have is that the bullets appear at all times, independent of whether the mouse button is pressed. If anyone can point out what it is that I'm doing wrong then I would be very grateful, here's the code (the canvass is created in separate HTML/CSS files):
var turn = 0;
var frameRate = 24;
var main_x = 0,
main_y = 0,
move_x = 0,
move_y = 0;
var gunfire = 0;
var speed = 4;
function keyPress(e) {
if (e.keyCode == 68) { //d
move_x = speed;
}
if (e.keyCode == 65) { //a
move_x = -speed;
}
if (e.keyCode == 83) { //s
move_y = speed;
}
if (e.keyCode == 87) { //w
move_y = -speed;
}
if (e.keyCode == 1) { //left mouse
gunfire = 1;
}
}
function keyRelease(e) {
if (e.keyCode == 68) { //d
move_x = 0;
}
if (e.keyCode == 65) { //a
move_x = 0;
}
if (e.keyCode == 83) { //s
move_y = 0;
}
if (e.keyCode == 87) { //w
move_y = 0;
}
if (e.keyCode == 1) { //left mouse
gunfire = 0;
}
}
function move() {
main_x += move_x;
main_y += move_y;
if (main_x < -220) {
main_x = -220
}
if (main_y < -220) {
main_y = -220
}
if (main_x > 220) {
main_x = 220
}
if (main_y > 220) {
main_y = 220
}
}
function mouseMove(e) {
var mouseX, mouseY;
if (e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
} else if (e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
mouseX = mouseX - (top_canvas.width / 2) - main_x;
mouseY = mouseY - (top_canvas.height / 2) - main_y;
window.radians = Math.atan2(mouseY, mouseX);
//document.getElementById("info").innerHTML = radians
}
var background = new Image();
background.src = "assets/background3.jpg";
var player1 = new Image();
player1.src = "assets/player1.png";
function draw() { //draws all content on the canvas
ctx_1.save();
ctx_1.clearRect(0, 0, top_canvas.width, top_canvas.height);
ctx_1.drawImage(background, 0, 0);
ctx_1.translate(top_canvas.width / 2 + main_x, top_canvas.height / 2 + main_y);
ctx_1.rotate(turn);
ctx_1.drawImage(player1, -25, -25, 50, 70);
if (gunfire = 1) {
ctx_1.fillStyle = "#000";
ctx_1.fillRect(0, -10, 200, 20);
}
ctx_1.restore();
}
function gameLoop() {
// all functions to update go here
draw();
turn = window.radians
move();
}
function init() {
window.top_canvas = document.getElementById("top_canvas");
window.ctx_1 = top_canvas.getContext("2d");
setInterval(gameLoop, 1000 / frameRate);
//event fires every time the mouse moves
top_canvas.addEventListener("mousemove", mouseMove, false);
window.addEventListener("keydown", keyPress, false);
window.addEventListener("keyup", keyRelease, false);
}
window.addEventListener("load", init, false);
First of all, as Kippie points out, you're setting gunfire to 1 each time which results in a true statement. It must be changed to == or === (latter preferable) or simply drop the comparison as the non-zero value would be true:
if (gunfire) {
...
}
Second, you are checking for mouse clicks inside a key handler and assuming keyCode would indicate a mouse button click. keyCode 1 is not related to mouse clicks at all and no mouse clicks will end up here.
Third, you are not having any event handler for mouse clicks...
To solve these issues (in addition to the first) add an event handler for mouse clicks:
top_canvas.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mouseup", mouseUp, false);
Then inside the handlers (only showing for mouse down):
function mouseDown(e) {
if (e.button === 0) { // check left mouse-button
gunfire = 1;
}
}
The reason for using window for mouse up is that in case your mouse pointer is outside the canvas element the up event won't register and the gun will continue to fire. Using the window object will allow you to register up event even in this situation.
if (gunfire = 1)
You're using the assignment operator, rather than doing a comparison.
Use === instead.

Prevent requestAnimationFrame from running all the time

I'd like to know how to call the animate function through requestAnimationFrame only when it's realy needed. Currently the animate is called all the time what generates an overhead I guess.
I already tried inside my animate function to compare targetRadius and the inital radius and return false once they are the same. Unfortunately this doesn't work at all.
Can someone explain me how to solve that?
jsfiddle
HTML:
<canvas id="ddayCanvas" width="288" height="288" data-image="http://www.topdesignmag.com/wp-content/uploads/2011/07/64.png">
<div>
<div class="product-image"></div>
<div class="product-box">...</div>
...
</div>
</canvas>
JS:
// Options
var maxImageWidth = 250,
maxImageHeight = 196;
var canvas = $('#ddayCanvas'),
canvasWidth = canvas.width(),
canvasHeight = canvas.height(),
sectorColor = $('.product-box').css('background-color'),
context = canvas[0].getContext('2d'),
imageSrc = canvas.data('image'),
imageObj = new Image(),
imageWidth, imageHeight,
mouseover = false;
imageObj.onload = function() {
imageWidth = this.width;
imageHeight = this.height;
if (imageWidth > maxImageWidth){
imageHeight = imageHeight - (imageWidth - maxImageWidth);
imageWidth = maxImageWidth;
}
if (imageHeight > maxImageHeight) {
imageWidth = imageWidth - (imageHeight - maxImageHeight);
imageHeight = maxImageHeight;
}
drawDday(90);
};
imageObj.src = imageSrc;
function drawDday (radius) {
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.drawImage(imageObj, Math.ceil((canvasWidth - imageWidth) / 2), Math.ceil((canvasHeight - imageHeight) / 2), imageWidth, imageHeight);
context.fillStyle = sectorColor;
context.beginPath();
context.rect(0, 0, canvasWidth, canvasHeight);
context.arc(canvasWidth/2, canvasHeight/2, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
// Check out the console
console.log('test');
}
var radius = baseRadius = 90,
targetRadius = 110,
ease = 50,
speed = 2;
function animate(){
if(mouseover){
radius += ((targetRadius-radius)/ease)*speed;
} else {
radius -= ((radius-baseRadius)/ease)*speed;
}
if(radius > targetRadius) radius = targetRadius;
if(radius < baseRadius) radius = baseRadius;
drawDday(radius);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
canvas.on('mouseover', function(e){
mouseover = true;
}).on('mouseout', function(){
mouseover = false;
});
You need to implement a condition so you can break the loop, for example (adopt as needed):
var isRunning = true;
function loop() {
... funky stuff here ...
/// test condition before looping
if (isRunning) requestAnimationFrame(loop);
}
Now when you set isRunning to false the loop will break. For convenience it's recommended that you have a method to start and stop the loop:
function startLoop(state) {
if (state && !isRunning) {
isRunning = true;
loop(); /// starts loop
} else if (!state && isRunning) {
isRunning = false;
}
}
The condition can be set by anything you need it to be set by, for example on a callback after an animation has finished etc. The important part is that the condition flag is available to both scopes using it (ie. most commonly in the global scope).
UPDATE:
More specific in this case is that your condition (radius) will never reach the condition required to eventually stop the loop.
Here is what you can do to fix this:
DEMO
var isPlaying = false;
function animate(){
/**
* To make sure you will reach the condition required you need
* to either make sure you have a fall-out for the steps or the
* step will become 0 not adding/subtracting anything so your
* checks below won't trigger. Here we can use a simple max of
* the step and a static value to make sure the value is always > 0
*/
if(mouseover){
radius += Math.max( ((targetRadius-radius)/ease)*speed, 0.5);
} else {
radius -= Math.max( ((radius-baseRadius)/ease)*speed, 0.5);
}
/**
* Now the checks will trigger properly and we can use the
* isPlaying flag to stop the loop when targets are reached.
*/
if(radius >= targetRadius) {
radius = targetRadius;
isPlaying = false; /// stop loop after this
} else if (radius <= baseRadius) {
radius = baseRadius;
isPlaying = false; /// stop loop after this
}
drawDday(radius);
/// loop?
if (isPlaying === true) requestAnimationFrame(animate);
}
In order to trigger the loop we use a method that will check if the loop is running, if not it will reset the isPlaying flag and start the loop. We do this inside both mouseover and mouseout:
canvas.on('mouseover', function(e){
mouseover = true;
startAnim();
}).on('mouseout', function(){
mouseover = false;
startAnim();
});
The method is simply checking isPlaying and if not set it set it to true and starts the loop - this so that the loop is only started once:
function startAnim() {
if (!isPlaying) {
isPlaying = true;
requestAnimationFrame(animate);
}
}
In the demo I added console logging to show when the loop is running and when targets are hit.
Hope this helps.
The reason your animate function is being called continuously is because you start off by calling requestAnimationFrame(animate); and then each call to animate unconditionally calls requestAnimationFrame(animate); again. The cycle is never going to be broken unless you use cancelAnimationFrame at some point (which you don't), or make sure that animate only requests another frame if it's needed.
Another issue is the fact that radius will currently never reach either targetRadius nor baseRadius, and therefore neither of the following will ever be true:
if(radius > targetRadius) radius = targetRadius;
if(radius < baseRadius) radius = baseRadius;
This isn't directly responsible for the continual calls to animate, but since targetRadius and baseRadius are being used to indicate the end-points of your animation then we need to form some sort of sensible conditional with them.
So, you could do something like: http://jsfiddle.net/PLDUq/9/
var radius = baseRadius = 50,
targetRadius = 110,
ease = 50,
speed = 12,
currentAnim;
function animate(){
if(mouseover){
radius += ((targetRadius-radius)/ease)*speed;
} else {
radius -= ((radius-baseRadius)/ease)*speed;
}
drawDday(radius);
if(Math.round(radius) >= targetRadius) {
// uses Math.round() to ensure condition can be fulfilled
radius = targetRadius;
return; // doesn't call next frame
}
if(Math.round(radius) <= baseRadius) {
radius = baseRadius;
return; // doesn't call next frame
}
requestAnimationFrame(animate);
}
canvas.on('mouseenter mouseleave', function (e) {
if (currentAnim) {requestAnimationFrame(currentAnim);}
// cancels current animation if one is playing to
// prevent several concurrent loops calling animate()
mouseover = (e.type === 'mouseenter');
requestAnimationFrame(animate);
});

Categories

Resources