I am currently making a slideshow that turns slides after 10 seconds, and using requestAnimationFrame to do so.
I am having some issues with the timestamp, however. I want to keep track of the timestamp value (which is no problem), and when it gets to a value over 10000 (10 seconds), to reset the timestamp to 0 and keep going. However, when I try to change the value of timestamp, nothing happens. Assuming it is a const? Setting a variable to performance.now() with each call is also not behaving quite like I'd expect.
Wondering what the best workaround to this issue would be, maybe some use of performance.now()? Thanks!
When the loop starts, store a reference to when it starts. Then update that variable, rather than trying to modify the timestamp.
let start = null;
let loop = (timestamp) => {
if (!start) {
start = timestamp;
};
const progress = timestamp - start;
if (progress > 10000) {
console.log('loop', start);
start = timestamp;
}
requestAnimationFrame(loop)
};
requestAnimationFrame(loop);
Related
I'm trying to make a script to increment a number to the selected number on every select change. For example, if the user selects 30, I want the number to increase at a rate of 30 values per second so it reaches 30 in one second.
I don't know what went wrong, but when executing this script, it only increments on the first page load but with no value change.
https://jsfiddle.net/User1010/b7znc3fL/
var valueElement = document.getElementById('value');
var option = document.getElementById('option');
var start = 0;
var end = parseFloat(option.innerHTML);
var duration = 1000; // In milliseconds (divide by 1000 to get seconds).
var framerate = 50; // In milliseconds (divide by 1000 to get seconds).
var toAdd = ( ( end - start ) * framerate ) / duration;
var interval = setInterval(function() {
var currentValue = parseFloat(valueElement.innerHTML);
if (currentValue >= end) {
clearInterval(interval);
return;
}
valueElement.innerHTML = (!isNaN(currentValue) == true ? (currentValue + toAdd).toFixed(2) : toAdd);
}, framerate);
You may be overthinking this task. I also found there were errors and things to change in the console and the JSFiddle. For example, there is no element with the name option.
https://www.khanacademy.org/computing/computer-programming/html-css-js/html-js-dom-animation/p/animating-dom-with-setinterval
https://www.khanacademy.org/computer-programming/spin-off-of-challenge-stopwatch/6144204027232256
Let's start with something basic: A Stopwatch
Define variables for better convenience, for better, more common practice, and for higher efficiency
A variable that can be called counterEl initializing the span element using document.getElementById() on the id 'daily'.
A variable that can be called selectEl initializing the select element using document.getElementById() on the id 'value'.
A type null variable that can be called currentTime which will turn counterEl into a float data type by calling parseFloat() on countEl.textContent.
A type null variable called stopwatch that will be initialized when you use setInterval.
I also used the linking Stack Overflow question for help
Add an event listener to the select element for every time its value changes like so: selectElement.addEventListener("change", myFunction);
Create a global function resetStopwatch() {}
Set countEl.textContent to 0.
Just for good measure, set currentTime to 0 as well.
stopwatch = window.setInterval(countUp, 1000);
Create the global countUp function
Everything here is explained in the comments.
// Turns the value into a float so it can be incremented and compared (textContent is a string)
currentTime = parseFloat(seconds.textContent);
// Add 1 second every time function is called
seconds.textContent = currentTime + 1;
if (seconds.textContent >= selectElement.value) {
window.clearInterval(stopwatch); // Stops the stopwatch if the seconds
reached the selected option
console.log("My time has been cleared");
}
Now let's slightly tweak this to make it a 'reverse stopwatch'
In the setInterval, you want it to increment that many in one second, so you would change the invocation to
stopwatch = window.setInterval(countUp, 1000/incrementRate.value);
Use my JS Fiddle for guidance in solving your problem:
https://jsfiddle.net/404_Error/z0t4spob/
Looks like you just need to bind a change event handler to your select/option.
Reference MDN's Event documentation on adding this to your script to handle the changes and update of the value.
Just a heads up, if you want to use a framework like jQuery, the process and script can be simplified drastically.
So I have a simple function:
var start = function(){
lastFrame = performance.now();
requestAnimationFrame((t)=>{interval(t)});
}
And my interval function (just for test purposes I have clogged the values of each rAF stamp)
function interval(t){
console.log (t);
console.log(lastFrame);
}
Now I have read the following response to another question but I just can't understand a few parts of this person's answer.
The timestamp passed in to the requestAnimationFrame() callback is the time of the beginning of the animation frame. Multiple callbacks being invoked during the same frame all receive the same timestamp. Thus, it would be really weird if performance.now() returned a time before the parameter value, but not really weird for it to be after that.
"it would be really weird if performance.now() returned a time before the paramter value" ?
Why would this be weird? I thought Javascript was an interpreted language? At this point:
lastFrame = performance.now();
The browser doesn't even now about the next line:
requestAnimationFrame((t)=>{interval(t)});
Surely if you make a call to performance.now() before you even provide a callback for your requestAnimationFrame the time of lastFrame should be less than the t passed into requestAnimationFrame?
In this person's response, he lays out a 6 step process involved with requesting an animation frame. However, he lists the performance.now() call as the last step.
How can it be the last step when it has been interpreted by the browser before the animationFrame request?
The answer you Pointyed to talks about calling performance.now() inside the callback of rAF. You are calling it outside, so of course, it will be set to a date before.
Your code, simplified.
let t0 = performance.now(); // This is called now
requestAnimationFrame(t1=>func(t1)); // this will be called at next screen refresh
// (somewhere between 0 and 17 ms later)
The other answer's Pointy:
let f1 = time => {
window.calledTime = time;
while(wait(a_few_seconds)){}
}
let f2 = time => {
time === window.calledTime; // true !
performance.now() - time; // approximately a_few_seconds
}
requestAnimationFrame(f1);
requestAnimationFrame(f2);
So with a few words, let's say that in your case it's normal that lastFrame is set before t, since it is called way before (rAF is async).
The other answer talked about chained rAF calls, which are all stacked in a same function call, and thus will all share the same timestamp argument, whatever the time previous callback took to execute.
Now, if what you need is an original timestamp, you're quite safe using performance.now() as a fallback, since it's similar to what is sent as argument to your callback, except that it reflects the time when the monitor sent its last V-Sync pulse, and is thus common to all callbacks of the same frame.
const loop = time => {
if(!time) time = performance.now(); // we called it out of a rAF routine
...
requestAnimationFrame(loop)
}
loop();
However, if you are using an rAF loop, most of the time, it's because you need your routine to be in sync with the screen. So it makes more sense to wait until the next screen refresh before starting your loop, otherwise, you'd execute this routine twice in a single frame.
const loop = time =>{
...
}
requestAnimationFrame(loop); // call it directly through rAF
I've also run into this. Seems like a bug, since it's so unintuitive.
I've resorted to using Date.now() for the initial value of elapsedTime.
var elapsedTime = 0;
var startTime = Date.now();
var lastTime = null;
requestAnimationFrame(update);
function update(currentTime) {
if (lastTime) {
elapsedTime += currentTime - lastTime;
} else {
elapsedTime = Date.now() - startTime;
}
// Compute the next value in the animation.
var value = computeValue(elapsedTime);
// Store the timestamp representing the animation frame.
lastTime = currentTime;
}
I want to reset the clock so that clock.getElapsedTime() gives me a new time from when I reset the clock (for example, useful when restarting a game level/scene the second time).
I am initiating clock = new THREE.Clock(); in init(), and in my game loop update(), I am using this clock. But when the game is over, I want to reset the clock (I am not initiating the level again and am just positioning the player back to the beginning so I am not initiating a new clock).
How can I achieve this?
Bad news: It's impossible to reset the THREE.Clock to zero time, as of r73, released Oct 2015. The explanation is below, and the only possible workarounds are at the end of this answer.
The Problematic Design of Three.Clock Investigated in Depth
The design of Clock is dangerous...
-- Mrdoob, GitHub issue comment
To understand the flaw(s) in THREE.Clock we must inspect the source code to see how it works. We can see that in the constructor for a Clock, a few variables are instantiated, which at first glace looks like we can overwrite on our Clock instance to reset to zero:
this.startTime = 0;
this.oldTime = 0;
this.elapsedTime = 0;
However, digging a little deeper, we need to figure out happens when getElapsedTime() is called. Under the hood, it calls getDelta(), which mutates this.elapsedTime, which means that getDelta and getElapsedTime are dangerous, conflicting functions, but we still need to look closer at getDelta:
var newTime = self.performance.now();
diff = 0.001 * ( newTime - this.oldTime );
this.oldTime = newTime;
this.elapsedTime += diff;
This function is referencing some unknown, implicit global variable, self, and calling some odd function, self.performance.now(). Red flag! But let's keep digging...
It turns out Three defines a global variable, self, with a property performance with a method now() in the "main" Three.js file.
Stepping back to THREE.Clock for a moment, look how it calculates this.elapsedTime. It's based on the value returned by self.performance.now(). That seems innocent enough on the surface, except the true problem arises here. We can see that self.performance.now() creates a true "private" variable in a closure, meaning no one in the outside world can ever see / access it:
( function () {
var start = Date.now();
self.performance.now = function () {
return Date.now() - start;
}
} )();
This start variable is the start time of the app, as returned in milliseconds from Date.now().
That means that when the Three.js library loads, start will get set to the value Date.now(). To clarify, simply including the Three.js script has global, irreversible side effects for the timer. Looking back on the clock, we can see that the return value of self.performance.now() is used to calculate the current elapsed time of a Clock. Because this is based on the private, inaccessible "start time" of Three.js, it will always be relative to when you include the Three.js script.
Workaround #1
Store startTime = clock.getElapsedTime() on your game/level start and making all your calculations relative to that. Something like var currentTime = clock.getElapsedTime() - startTime which is the only way to get the true absolute time from your scene load / start.
Workaround #2
Three.Clock under the hood is really just a thin wrapper around Date.now(), which is an IE9+ available method. You're probably better off creating your own tiny abstraction around this to get a sane clock, with an easy to implement reset() method. If I find an npm package with this functionality, or make my own, I will update this answer.
Workaround #3
Wait for the Three.js Three.Clock source code to be updated, possibly by me. Since there is an open ticket for it, a pull request to fix it will likely be accepted.
Save the time at the start of the game: var gameStartTime = clock.performance.now(), and calculate the run of time thereafter by subtracting gameStartTime from clock.performance.now() at any other point during the game, including its end. For example: gameStartTime - gameStartTime would equal zero, and clock.performance.now() - gameStartTime would give you the seconds or minutes since the game began.
Here's a reference to the performance.now() timer function in JavaScript.
This will not reset the clock but it will generate a new clock object and assign the new value to the elapsedTime variable every 5 seconds:
let clock = new THREE.Clock();
const animate = () => {
let elapsedTime = clock.getElapsedTime();
if (elapsedTime > 5) {
clock = new THREE.Clock();
}
window.requestAnimationFrame(animate);
};
Here is simple workaround I use to start pause and reset the runtime of my animation systems.
let offsetTime = 0; // in milliseconds
let start = false;
function reset() {
offsetTime = 0;
start = false;
}
function start() {
if ( start ) return;
offsetTime += performance.now();
start = true;
}
function pause() {
if ( start ) return;
offsetTime -= performance.now();
start = false;
}
const animationRuntime = performance.now() - offsetTime;
I have a problem trying to know if a setInterval() is taking place or it was killed.
I am creating an interval and saving it into a variable:
interval = setInterval('rotate()',3000);
Then on click to an element I stop the interval and wait 10 seconds before starting a new one, by the way the variable interval is global:
$(elm).click(function(){
clearInterval(interval);
position = index;
$('#banner div').animate({
'margin-left':position*(-img_width)
});
setTimeout('startItnerval()',10000);
});
function startItnerval(){
interval = setInterval('rotate()',3000);
}
It seems to work but eventually I can realize that there are intervals still being in place, everytime I start a new interval it is saved in the interval variable, which is global, so in theory even if I start 100 intervals they are all saved in the same variable replacing the previous interval right? So I should only have one instance of interval; then on clearInterval(interval); it should stop any instance.
After looking at the results, apparently even if it is saved in the same variable, they are all separate instances and need to be killed individually.
How can I trace how many intervals are being executed, and if possible identify them one by one? even if I am able to solve the problem I really would like to know if there is a way to count or show in the console how many intervals are being executed?
thanks
jsFiddle Demo
As pointed out in comments, the id's constantly increase as timers are added to a page. As a result, it may be possible to clear all timers running on a page like this:
function clearTimers(){
var t = window.setTimeout(function(){
var idMax = t;
for( var i = 0; i < idMax; i++ ){
window.clearInterval(i);
window.clearTimeout(i);
}
},4);
}
The reason that you can only see one interval is because every time you start a new interval, you overwrite the value in interval. This causes the previous intervals to be lost but still active.
A suggestion would be to just control access to your variable. Clearly there is an issue where the start function is called too often
clearInterval(interval);//when you clear it, null it
interval = null;
and then take advantage of that later
if( interval != null ){
interval = setInterval('rotate()',3000);
}
Also, as Pointy noted in a comment, using a string to call a function is not best practice. What it basically does is converts it into a Function expression which is similar to using eval. You should probably either use the function name as a callback
setInterval(rotate,3000);
or have an anonymous function issue the callback
setInterval(function(){ rotate(); },3000);
setInterval returns an Id, not the actual object, so no, no interval will be overriden if you repeat the line
var xy = setInterval(function() {...}, 1000);
If you want to stop the interval you have to clear it:
clearInterval(xy);
And if your startInterval can be called multiple times in a row, but you don't want to create multiple intervals, just clear the inverval before you start a new one:
function startInterval(){
clearInterval(interval);
interval = setInterval('rotate()',3000);
}
If you have to create multiple intervals, you could save the ids in an array to keep track of them:
var arr = [];
//set the interval
arr.push(setInterval(...));
//get number of currently running intervals
var count = arr.length //gives you the number of currently running intervals
//clear the interval with index i
clearInterval(arr[i]);
arr.splice(i, 1);
I have the following script in a js file:
// Ad score
var score = 0;
//$('#score').text(score);
function foundMatchingBlocks(event, params) {
params.elements.remove();
score += 100;
$('#score').text(score);
};
Now on each matching, 100 points are added to var score. This all works. Now I want to extend this a bit. As soon as the page loads I want to start a countdown to reduce the number of points (starting with 100) with 1 point a second for 60 seconds. So the minimum number of points a user can get is 40. When someone gets the points, the counter should reset and countdown again.
Example:
Page loads (timer starts from 100)
User has a match after 10 seconds (+90 points are added)
Counter resets and countdown from 100 again
User found a match after 35 sec (+65 points are added)
etc etc
Problem is, I have no idea how to do this :( Hope someone can help me with this.
The above is fixed, thanks all for helping!!!
The big picture is, you'll need to become pretty familiar with timeouts and intervals in javascript. This is the reference page I keep going back to when I need to refresh my memory: http://www.elated.com/articles/javascript-timers-with-settimeout-and-setinterval/
For your specific task, you'll probably want to use an Interval that triggers every 1000 milliseconds to calculate the second-by-second point reduction, and a separate Timeout for failure that resets every time the user completes their challenge.
Here are a few tips for working with timeouts and intervals that usually lead to followup questions:
When you set a timeout, always capture the return value (I think it's basically a random integer). Save it to some global var for convenience.
var failureTimer; // global var high up in your scope
failureTimer = setTimeout ( "gameOver()", 100000 ); // 100 seconds * 1000ms
Then in whichever method gets called when the player completes their challenge, you call this:
clearTimeout (failureTimer); // resets the timer and gives them another 100 seconds
failureTimer = setTimeout ( "gameOver()", 100000 ); // yes call this again, to start the 100 sec countdown all over again.
The second pain point you're likely to encounter when working with Timeouts and Intervals is how to pass parameters to the functions like gameOver() in my example above. You have to use anonymous functions, as described here:
Pass parameters in setInterval function
For more on anonymous functions, this is a good overview:
http://helephant.com/2008/08/23/javascript-anonymous-functions/
Good luck with your project! Let me know if you have any questions.
Here's some code without the use of timers. Call startCountdown() every time you want to re-initialize the count-down. Call getAvailableScore() when you want to fetch the current available score. You will have to decide what to do when the available score goes to zero.
var beginCountDownTime;
function startCountdown() {
beginCountDownTime = new Date();
}
function getAvailableScore {
var now = new Date();
var delta = (now.getTime() - beginCountDownTime.getTime()) * 1000; // time elapsed in seconds
var points = 100 - (delta / 60);
return(Math.round(Math.max(points, 0))); // return integer result >= 0
}
Maybe something like:
// Ad score
var score = 0;
var pointsAvailable = 100;
//$('#score').text(score);
function foundMatchingBlocks(event, params) {
params.elements.remove();
score += pointsAvailable;
$('#score').text(score);
pointsAvailable = 100;
};
$(document).ready(function() {doTimer();});
function doTimer() {
setTimeout('reducePoints()',1000);
}
function reducePoints() {
if(pointsAvailable>40) {
pointsAvailable--;
}
doTimer();
}