"while" loop to change interval of "setTimeout" NOT WORKING - javascript

I created a rectangle , and i want to give it a speed that change over time (acceleration) .
So I made a "setTimeout" inside a "while" loop.
Supposedly , the "while" loop should continuously change the interval of the "setTimeout" (var=interval) by -1 , but instead it replaces it with 1 !!, wich makes the rectangle print every 1 milliseconds .
I would like to know why this happens .
the same thing happens if i use the "for" loop.
and i wouldn't mind any other alternative to create acceleration effect.
thank you
var canvas = document.getElementById("canvas")
var context = canvas.getContext("2d")
var posX=20;
var posY=20;
var interval = 500;
function print () {
//background
context.fillStyle="black";
context.fillRect(0, 0, 500, 500);
//object
context.fillStyle="#4286f4";
context.fillRect(posX, posY, 50, 50);
posX = posX + 1;
posY = posY + 1;
}
while (interval > 300) {
interval-- ;
setTimeout(print, interval);
}
<!DOCTYPE html>
<html>
<head>
<title>Particles</title>
<meta charset="UTF-8"/>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
</html>

the "while" loop should continuously change the interval of the "setTimeout"
No, it schedules a new timer at the new interval. The previous one keeps running as well. You very, very, very quickly end up with a bunch of timers pending -- which all then expire one right after the next.
Once you start a timer, you can't change when it fires. You can cancel it, but you can't change when it fires.
setTimeout also schedules a single timed callback. If you want repeated ones, use setInterval or schedule a new callback from the setTimeout callback when it runs.
I recommend taking a step back and experimenting with the basics of timers and intervals before moving on to something complex like doing animations.
Separately: setTimeout and setInterval are the wrong tools for animation, at least in isolation. Instead, when you know you need to update the circle, use requestAnimationFrame to have the browser call you immediately before it renders the display (it will fire ~60 times/second, so only request it when you need it). That helps you coordinate with the browser's internal display cycles.
Something along these lines:
scheduleNext();
function scheduleNext() {
if (interval > 300) {
setTimeout(function() {
requestAnimationFrame(function() {
print();
scheduleNext();
});
}, interval);
}
}
Live Example:
var canvas = document.getElementById("canvas")
var context = canvas.getContext("2d")
var posX = 20;
var posY = 20;
var interval = 500;
function print() {
//background
context.fillStyle = "black";
context.fillRect(0, 0, 500, 500);
//object
context.fillStyle = "#4286f4";
context.fillRect(posX, posY, 50, 50);
posX = posX + 1;
posY = posY + 1;
}
scheduleNext();
function scheduleNext() {
if (interval > 300) {
setTimeout(function() {
requestAnimationFrame(function() {
print();
scheduleNext();
});
}, interval);
}
}
<canvas id="canvas" width="500" height="500">

setTimeout doesn't pause the script, it schedules the callback to be run in the given milliseconds. This means that you're scheduling all the callbacks right away, and there's only a 1 millisecond difference between them, which isn't enough to cause an observable difference.

Update :
ahaa , Yes its possible to create the animation loop with just "setTimeout" ,
function test () {
print ();
interval = interval - 10;
setTimeout(test, interval);
}
test ();

Related

Why canvas drawing is asynchronous, but API is synchronous?

I am writing a vizualization of neural network, and I would like to redraw it on each training iteration, so I have next button with onclick callback:
startButton.onclick = () => {
for (let i = 0; i < trainData.length; i++) {
setTimeout(() => {
network.trainSample(trainData[i])
ctx.clearRect(0, 0, width, height)
drawNN(network)
}, 0)
}
}
The problem is, if I take off setTimeout, it will execute all the training, and redraw everything in the end.
As far as I know, there is an event loop and what setTimeout trick does, it creates a Job in a event queue that will be executed not exactly now, but as soon as possible.
Okay, but if canvas drawing is asynchronous and drawing get's postponed till the end, why it's api is synchronous?
Minimal example:
const canv = document.getElementById('myCanv')
const ctx = canv.getContext('2d')
ctx.strokeStyle = '#000000'
for (let x = 0; x <= 100; x++) {
ctx.clearRect(0, 0, 100, 100)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(x, 100)
ctx.stroke()
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas width="100" height="100" id="myCanv"></canvas>
</body>
</html>
Your usage is incorrect and even if canvas drawing was synchronous you most likely would see only the last frame anyway with some way too fast weird animation in between. What you need is instead of standard loop use some sort of animation loop. For example:
let i = 0;
function animationLoop() {
if (i < trainData.length) {
network.trainSample(trainData[i]);
ctx.clearRect(0, 0, width, height);
drawNN(network);
i++;
requestAnimationFrame(animationLoop);
}
}
requestAnimationFrame(animationLoop);
Here I am using requestAnimationFrame which would result in around 60 frames per second. My guess for your case this might still be too fast. You can limit frames per second using additional setTimeout inside animateLoop function.

How to have setInterval start at button press without breaking clearInterval inside function?

I have a setInterval that draws my snake game, and everytime the snake "eats" I have a clearInterval and then the setInterval var with adjusted speed. The original setInterval starts on load. How can I make it start with the press of a button?
My attempts at placing the setInterval inside the button and using functions have worked in making the button start the setInterval, but it always breaks my clearInterval inside the draw function.
<button onclick="">Start game</button>
if (snakeX == food.x && snakeY == food.y){
food = {
x : Math.round(Math.random()*(cvsWidth/snakeWidth-1)),
y : Math.round(Math.random()*(cvsHeight/snakeHeight-1))
};
score++;
if (speed >= 40) speed = speed-3;
clearInterval(interval);
interval = setInterval(draw, speed);
}else{
//Remove last
snake.pop();
}
var speed = 140;
var interval = setInterval(draw, speed);
By setting the onclick attribute of your button to start() causes start to be called when you click the button.
I also declare interval as a global variable since I think you need it to be global.
Then when start gets called then the interval gets started and the id of the interval is put into interval
var speed = 500;
function draw() {
console.log("Running")
}
var interval;
function start() {
//Prevent start from running twice.
if(interval != null) return;
interval = setInterval(draw, speed);
}
<button onclick="start()">Start game</button>

Capture photos from video after specific time in p5.js

var video;
var snapshots = [];
var readyCheck = false;
var button;
function setup() {
createCanvas(800, 600);
background(0);
video = createCapture(VIDEO, ready);
video.size(200, 150);
}
function ready() {
readyCheck = true;
console.log('work');
}
function draw() {
var w = 200;
var h = 150;
var x = 0;
var y = 0;
if (readyCheck) {
for (var i = 0; i < 100; i++) {
// use setTimeout() to wait for 2 seconds
setTimeout(function() {
snapshots[i] = video.get();
image(snapshots[i],x, y);
x += w;
if (x >= width) {
x = 0;
y += h;
}
}, 2000);
}
}
}
my purpose is taking pictures from the webcam after specific time. So I use the setTimeout() in JS. I expect pictures will appear on the canvas every 2 seconds in a row.
when entering the for part, the code will wait 2 seconds and capture the image from webcam and display it.
but my situation is that all the picture appear on the canvas at the same time.
You need to take a step back and understand how the draw() function and the setTimeout() functions work.
The draw() function is automatically called 60 times per second. You can adjust this by calling the frameRate() function or the noLoop() function. More info is available in the reference.
The setTimeout() function sets up a callback function that is automatically called after some duration, in your case 2 seconds.
So, what your code is doing is setting up 100 callback functions that will all fire in 2 seconds- and it's doing this 60 times per second! So in 1 second, you'll have 6000 functions that will start firing 2 seconds later! This is almost definitely not what you want.
P5.js already has its own timing mechanism in the draw() function that's called 60 times per second, so it seems a little weird to use the setTimeout() function inside P5.js code. Instead, you should probably set up your own timing using the frameCount variable or the millis() function.
Here's an example that shows a random color every second:
function setup() {
createCanvas(200, 200);
}
function draw() {
if(frameCount % 60 == 0){
background(random(256), random(256), random(256));
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/p5.min.js"></script>
This code uses the frameCount variable and the % modulus operator to check whether 60 frames have passed, and if so, it sets the background to a random color. You'll want to do something similar.
Like I said above, more info about all of this can be found in the reference.

simple animation with javascript interval

I'm set up a simple animation with set Interval.
But I wanted to do an animation where it moves to a target position over time.
I used to this back in the flash days and have forgotten the process.
It remember using something like
property = (target - property)/speed;
But having a problem setting that up with the below setup.
I understand there is a 100 ways to do this and even using css but I just want to know how I could implement with that I have now. I just want that ease over time to happen with setInterval.
var sq = document.querySelector('.square');
button = document.querySelector('button');
var interval, toggle = 0, pos=0;
var targetX = 100;
var startX = 0;
button.addEventListener("click", (event) =>{
toggle += 1;
toggle = toggle % 2;
if( toggle > 0){
interval = setInterval(animate, 5);
}else{
clearInterval(interval);
}
});
function animate(){
//pos++
//sq.style.left = pos + 'px';
sq.style.left = (targetX - sq.style.left) / speed
}
Using setInterval() isn't the best choice for animations since it cannot sync to monitor updates. A better choice, and highly recommended, is requestAnimationFrame().
Right now the code uses an interval of 5ms which is way too low. The shortest frame update happens at 16.7ms (at 60Hz screens) so there is much overhead here.
I would also suggest using linear interpolation (AKA lerp) to do the animation, and time duration to define speed. Using interpolation allows you to further feed the t into an easing function.
Example
t is calculated based on start-time, current time and duration
t, now normalized to [0, 1] is fed to lerp function to obtain new position
translateX is used to move div to allow subpixeled animation. Final position can still be set by removing translation and a fixed position for left.
var reqId, // request ID so we can cancel anim.
startTime = 0,
source = 0,
target = 300,
duration = 1000, // duration in ms
sq = document.querySelector("div");
document.querySelector("button").onclick = function() {
startTime = 0; // reset time for t
cancelAnimationFrame(reqId); // abort existing animation if any
reqId = requestAnimationFrame(loop); // start new with time argument
function loop(time) {
if (!startTime) startTime = time; // set start time if not set already
t = (time - startTime) / duration; // calc t
var x = lerp(source, target, t); // lerp between source and target position
sq.style.transform = "translateX("+x+"px)"; // use transform to allow sub-pixeling
if (t < 1) requestAnimationFrame(loop); // loop until t=1
// else { here you can remove transform and set final position ie. using left}
}
// basic linear interpolation
function lerp(v1, v2, t) {return v1 + (v2-v1) * t}
};
div {position:absolute; left:0; top:40px; width:100px;height:100px;background:#c33}
<button>Animate</button>
<div></div>

calling setTimeout again stops first instance

I have a trivial little game I wrote in javascript that creates a wave from where you click. I figured out two ways to make the "wave" move across the screen:
by calling jQuery.animate() with increasingly large times. demo
by recursvely calling setTimeout. demo
The problem I have is that I want the behavior of 2 (columns all grow at the same speed with an offset timing) but with the action of 1 (subsequent clicking compounds the "waves" formed).
Right now on the second demo, if you click again before the "wave" is finished, it immediately clears the setTimeouts and starts them all over again. I want to be able to stack them like in the first example.
You can view the source of either of those pages to see the code (methods are getMouseXYUp and getMouseXYDown).
the basic gist of what i am doing in the second demo is found in these two functions:
function getMouseXYUp(e) {
$("body").die('mousemove');
h = (document.height - e.pageY + 17);
left = id-1;
right = id+1;
setTimeout("moveleft()", 100);
setTimeout("moveright()", 100);
return true
}
function moveleft(){
if (left >= 0){
$("#div"+left).animate({'height': h}, 400);
left--;
setTimeout("moveleft()", 50);
}
}
The problem is that you're resetting the variables "left" and "right" as soon as the second mouse click happens. Is this any closer to what you're looking for?
function getMouseXYUp(e) {
$("body").die('mousemove');
var h = (document.height - e.pageY + 17);
var left = id-1;
var right = id+1;
setTimeout(function() { moveleft(left, h); }, 100);
setTimeout(function() { moveright(right, h); }, 100);
return true;
}
...
function moveleft(left, h) {
if (left >= 0){
$("#div"+left).animate({'height': h}, 400);
left--;
setTimeout(function() { moveleft(left, h); }, 50);
}
}
function moveright(right, h) {
if (right < divs){
$("#div"+right).animate({'height': h}, 400);
right++;
setTimeout(function () { moveright(right, h); }, 50);
}
}
Looking at the code, looks like you have issues with global variables. You would need to pass along left and right in the function calls and not have it in a global scope.

Categories