I have my object called player
var player = new Object();
player = {
x: (window.innerWidth / 2),
y: (window.innerHeight / 2),
width: 30,
height: 30,
color: 'red'
};
And some variable to check mouse down clicks
var mouseDown = 0;
And I have functions that is firing every 40ms...
function drawPlayer() {
var toPlayerX;
var toPlayerY;
var toPlayerLength;
toPlayerX = player.x - mouseX;
toPlayerY = player.y - mouseY;
toPlayerLength = Math.sqrt(toPlayerX * toPlayerX + toPlayerY * toPlayerY);
toPlayerX = toPlayerX / toPlayerLength;
toPlayerY = toPlayerY / toPlayerLength;
toPlayerLength = toPlayerLength - (toPlayerLength%1);
// this function get reduced distance between mouse and player canvas rect by 1px per click
function movePlayer() {
player.x -= toPlayerX;
player.y -= toPlayerY;
}
// on MOUSE DAWN EVENT
document.body.onmousedown = function() {
// on every mouse down click ++ mousedawn
++mouseDown;
// here we fire interval to make player alive and
// follow to mouse dawn click by ~25 pixels per secord
if (mouseDown==1) {
setInterval(movePlayer,40);
}
if (mouseDown>1) {
clearInterval(movePlayer());
mouseDown = 0;
}
};
}
The root of the problem is in this part
if (mouseDown>1) {
clearInterval(movePlayer());
mouseDown = 0;
}
I thought it will be clear movePlayer interval if mouseDown will have number of two, but instead the setInterval(movePlayer) just multiplie all the time while I click mouse, and making mouseDown = 0 works pretty well when it's go number of 2.
At the beginning I just wanna make code so when user clicks in some area the canvas player would follow straightly on area where the mouse was clicked and then stopped and while for example player is going somewhere already and the mouse was click again to change canvas first destination to area where the mouse was clicked last.
You have to set the setInterval command to a variable, and that is what you use to clear it.
So your code would look something like this:
// this function get reduced distance between mouse and player canvas rect by 1px per click
function movePlayer() {
player.x -= toPlayerX;
player.y -= toPlayerY;
}
var intervalID; // Declare the variable that is storing the interval.
// on MOUSE DAWN EVENT
document.body.onmousedown = function() {
// on every mouse down click ++ mousedawn
++mouseDown;
context.save();
context.fillStyle = 'black';
context.restore();
// here we fire interval to make player alive and follow to mouse dawn click by ~15 pixels per secord
if (mouseDown==1) {
intervalID = setInterval(movePlayer,40);
}
if (mouseDown>1) {
clearInterval(intervalID);
mouseDown = 0;
}
};
document.body.onmouseup = function() {
//--mouseDown;
};
clearInterval takes an intervalID returned from setInterval.
var timeoutId = 0;
if (mouseDown==1) {
timeoutId = setInterval(movePlayer,40);
}
if (mouseDown>1) {
clearInterval(timeoutId);
mouseDown = 0;
}
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval
Related
I am evaluating whether I want to use EaselJS to make a JS image viewer/editor. One necessary feature is the scroll to zoom feature. This means when you have your mouse over a bitmap and you move the scroll wheel, the image scales properly.
I am using the EaselJS drag and drop demo to try scroll to zoom out (https://www.createjs.com/demos/easeljs/draganddrop). I am having trouble finding the event that fires when you move the scroll wheel.
This is the event I tried to add the following event to the bitmap:
bitmap.on("mousewheel", function (evt) {
this.scale = this.scale * 2;
update = true;
});
I also tried the following without success:
bitmap.on("wheel", function (evt) {
this.scale = this.scale * 2;
update = true;
});
and
bitmap.on("scroll", function (evt) {
this.scale = this.scale * 2;
update = true;
});
Here is the demo code in full:
var canvas, stage;
var mouseTarget; // the display object currently under the mouse, or being dragged
var dragStarted; // indicates whether we are currently in a drag operation
var offset;
var update = true;
function init() {
examples.showDistractor();
// create stage and point it to the canvas:
canvas = document.getElementById("testCanvas");
stage = new createjs.Stage(canvas);
// enable touch interactions if supported on the current device:
createjs.Touch.enable(stage);
// enabled mouse over / out events
stage.enableMouseOver(10);
stage.mouseMoveOutside = true; // keep tracking the mouse even when it leaves the canvas
// load the source image:
var image = new Image();
image.src = "../_assets/art/daisy.png";
image.onload = handleImageLoad;
}
function stop() {
createjs.Ticker.removeEventListener("tick", tick);
}
function handleImageLoad(event) {
var image = event.target;
var bitmap;
var container = new createjs.Container();
stage.addChild(container);
// create and populate the screen with random daisies:
for (var i = 0; i < 100; i++) {
bitmap = new createjs.Bitmap(image);
container.addChild(bitmap);
bitmap.x = canvas.width * Math.random() | 0;
bitmap.y = canvas.height * Math.random() | 0;
bitmap.rotation = 360 * Math.random() | 0;
bitmap.regX = bitmap.image.width / 2 | 0;
bitmap.regY = bitmap.image.height / 2 | 0;
bitmap.scale = bitmap.originalScale = Math.random() * 0.4 + 0.6;
bitmap.name = "bmp_" + i;
bitmap.cursor = "pointer";
// using "on" binds the listener to the scope of the currentTarget by default
// in this case that means it executes in the scope of the button.
bitmap.on("mousedown", function (evt) {
this.parent.addChild(this);
this.offset = {x: this.x - evt.stageX, y: this.y - evt.stageY};
});
// the pressmove event is dispatched when the mouse moves after a mousedown on the target until the mouse is released.
bitmap.on("pressmove", function (evt) {
this.x = evt.stageX + this.offset.x;
this.y = evt.stageY + this.offset.y;
// indicate that the stage should be updated on the next tick:
update = true;
});
bitmap.on("rollover", function (evt) {
this.scale = this.originalScale * 1.2;
update = true;
});
bitmap.on("rollout", function (evt) {
this.scale = this.originalScale;
update = true;
});
bitmap.on("mousewheel", function (evt) {
this.scale = this.scale * 2;
update = true;
});
}
examples.hideDistractor();
createjs.Ticker.addEventListener("tick", tick);
}
function tick(event) {
// this set makes it so the stage only re-renders when an event handler indicates a change has happened.
if (update) {
update = false; // only update once
stage.update(event);
}
}
I am expecting the image to scale by a factor of 2 whenever I scroll while the mouse is over a bitmap. Please let me know if anyone has any ideas on how to do this properly.
document.getElementById('canvas').addEventListener('wheel', moverContenido.bind(this));
var i = 0;
function moverContenido(accion) {
if(accion.wheelDelta > 0 || accion.detail > 0) {
i++;
} else if(accion.wheelDelta < 0 || accion.detail < 0) {
i--;
}
}
I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.
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.
I have a image and I'm trying to animate it through setInterval() . My aim is to move the image up by a certain pixel only when the user clicks on the Canvas else the image should be moving downwards.Everything goes well but the timer increases the speed for every onclick()
my canvas tag
<canvas id="myCanvas" width="400" height="600" onclick=init()></canvas>
java script:
function init()
{
function draw1()
{
context.clearRect(0,0,400,600);
img_y = img_y - 40;
context.drawImage(image1,img_x,img_y);
}
function move()
{
context.clearRect(0,0,400,600);
img_y = img_y + 7;
context.drawImage(image1,img_x,img_y);
}
draw1();
setInterval(move,70);
}
My animation starts when the user clicks the Canvas of my game. When I click the Canvas for the second time or so,the animation speed increases. What is wrong with my logic?
Your problem is that each click ADDS an interval. So your code basically runs more times than you want (making your animation faster). Make sure to clear the interval before starting a new one. Try the following (docs):
window.myTimer = '';
function init()
{
function draw1()
{
context.clearRect(0,0,400,600);
img_y = img_y - 40;
context.drawImage(image1,img_x,img_y);
}
function move()
{
context.clearRect(0,0,400,600);
img_y = img_y + 7;
context.drawImage(image1,img_x,img_y);
}
draw1();
window.clearInterval(window.myTimer); // clear previous interval
window.myTimer = setInterval(move,70); // set it again
}
As #Pointy pointed out,, you are increasing the 'speed' every click, because you are creating a new interval which is calling that same function.
Consider either clearing the interval and making a new one each time : window.clearInterval(intervalId);
OR
implementing a bool within your function which will prevent its movement until click.
You are mixing up the code that initiates movement with the code that moves the object up. They need to be separate functions.
<canvas id="myCanvas" width="400" height="600" onclick="moveUp"></canvas>
JS:
var movement = 7, timer;
function animate() {
context.clearRect(0, 0, 400, 600);
img_y += 7;
context.drawImage(image1, img_x, img_y);
}
function moveUp() {
// no need to redraw the image here as it will be drawn at the next frame anyway
img_y -= 40;
}
function init() { // call this when you want the main animation to start
if (timer) { // if timer variable already exists
clearInterval(timer); // clear the corresponding interval
timer = null; // and also clear the variable itself
}
timer = setInterval(animate, 70);
}
(This code clears the timer before setting it, in case, for some reason, init is called more than once.)
You could also work with the onmouseup, onmousedown events, letting it move as long mouse is pressed, and stop moving when mouse is no longer pressed. This way you can create and remove the interval on both events
var interval;
function startInterval() {
interval = setInterval(animation.move.bind(animation), 70);
}
function stopInterval() {
clearInterval(interval);
}
var animation = {
old: undefined,
x: 0,
y: 0,
context: undefined,
move: function() {
this.y += 10;
animation.draw();
},
draw: function() {
if (typeof animation.context === 'undefined') {
var el = document.getElementById('ctx');
animation.context = el.getContext('2d');
}
var context = animation.context;
if (typeof animation.old !== 'undefined') {
context.clearRect(animation.old.x, animation.old.y, 10, 10);
}
animation.old = {
x: animation.x,
y: animation.y
};
context.fillStyle = "#FF0000";
context.fillRect(animation.old.x, animation.old.y, 10, 10);
}
};
<canvas id="ctx" width="300" height="300" onmousedown="startInterval()" onmouseup="stopInterval()"></canvas>
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);
});