I'm building a gantt chart style timeline using html canvas element.
I am currently attempting to add the functionality which allows the user to click a next/prev button to have the gantt chart scroll to display earlier or later times.
The way I am doing this is to have a span.adjustTime where the id holds a value in seconds for the time to be adjusted (eg 86400 for one day).
I am trying to animate the scrolling so it looks like a scroll, rather than jumping ahead by one day.
I have a small problem in my timing calculation, but the script below is not animating, but rather jumping directly to the final time.
I do have the draw function running on a separate setInterval which updates every second, so I'm hoping it isn't an issue of conflicting timers on the same function on the same element and data.
jQuery('span.adjustTime').click(function() {
var adjustBy = parseInt(jQuery(this).attr('id').replace('a', ''));
var data = jQuery('img#logo').data();
for(var m = 1; m >= 30; m++) {
gantt.startUnixTime = gantt.startUnixTime + (adjustBy * (m * 0.001));
var moveTimer = setTimeout(function() {
draw(document.getElementById('gantt'), data, gantt);
}, 1000);
if (m == 30) {
clearTimeout(moveTimer);
}
}
});
In the for loop you are calling setTimeout 30 times, each time with the same timeout value of 1000. So after 1000 milliseconds 30 scheduled functions will execute at almost the same time. I suppose this is not what you intended. If you wanted an animation of 30 frames over 1000 milliseconds, the setTimeout should look something like:
setTimeout(function() { ... }, 1000 / 30 * m)
Also note that all 30 scheduled functions will see the same gantt.startUnixTime value, since the same object (gantt) is passed to all of them, and when they execute, the for loop has finished already long ago.
Related
Trying to maintain 60 fps animation. Currently, I'm getting a lot of spikes of slow frames that are happening at like 30~ fps and the choppiness is noticeable to my eye.
Significant Edits: Throwing out old obsolete code, adding in new code with explanation
fadeIn: function(ele){
raf = window.requestAnimationFrame(function() {
console.log(ele);
var opacity = 0;
function increase () {
opacity += 0.05;
if (opacity >= 1){
// complete
ele.style.opacity = 1;
return true;
}
ele.style.opacity = opacity;
requestAnimationFrame(increase);
}
increase();
});
},
fadeInElements: function(elements, properties, speed, delay){
var raf;
var ele;
for (i = 0; i < properties.length; i++){
ele = elements[properties[i]];
console.log('outside loop ' + ele);
instance.fadeIn(ele);
}
},
My new code is above. It is successfully:
Iterating through several elements (each as ele) and then calling fadeIn(ele)
So, all elements fade in.
However, I want a 50ms delay between each "fade in" (each triggering of fadeIn() on a new element
The good news is that it's not actually recursion — it's more like a timeout. You provide a function that draws a frame, and the browser calls it for you.
Here's an answer showing excerpts from a complete JSFiddle. It doesn't try to replicate your exact code, but instead tries to explain what you need to know so you can adapt your code accordingly. The code was written to be easy to understand, so I'm sure there are other ways to do it faster!
This answer works from the top level down, which means I describe the end of the JSFiddle source and work my way backwards. I personally think this makes it easier to understand than does starting with the details.
You need to start the animation somewhere. So the very last thing in the JSFiddle is
window.requestAnimationFrame(eachFrame); //start the animation
This will call a function called eachFrame() when it's time for the next frame, e.g,. on the next multiple of 60 times per second. It will only do it once, though.
You need eachFrame() to keep track of where we are in the animation.
var startTime = -1.0; // -1 = a flag for the first frame.
function eachFrame()
{
// Render this frame ------------------------
if(startTime<0) {
// very first frame (because of the -1.0): save the start time.
startTime = (new Date()).getTime();
render(0.0);
// the parameter to render() is the time within the
// animation.
} else {
// every frame after the first: subtract the saved startTime
// to determine the current time.
render( (new Date()).getTime() - startTime );
}
// Now we're done rendering one frame. ------
//Start the timer to call this function again
//when it's time for the next frame.
window.requestAnimationFrame(eachFrame);
}; //eachFrame
eachFrame() determines what the current time is with respect to the beginning of the animation. getTime() gives you the time in milliseconds.
The other thing eachFrame() does is to call window.requestAnimationFrame(eachFrame); again. This isn't recursion. Instead, eachFrame() will finish running, and then after that, the next time a frame comes around, the browser will call eachFrame() again.
The last function you need is something to actually draw the frame! That is render(current time). Assume that, e.g., head1 and head2 refer to two heading elements you want to animate, e.g., <h1> elements declared in your HTML. The clamp(x) function returns x but clamped below at 0 and above at 1.
function render(currTime)
{ // *** Put your rendering code here ***
// How opaque should head1 be? Its fade started at currTime=0.
var opacity1 = clamp(currTime/FADE_DURATION);
// over FADE_DURATION ms, opacity goes from 0 to 1
// How opaque should head2 be?
var opacity2 = clamp( (currTime-FADE_SPACING)/FADE_DURATION );
// fades in, but doesn't start doing it until
// FADE_SPACING ms have passed.
// Apply the changes
head1.style.opacity = opacity1;
head2.style.opacity = opacity2;
} //render
In render(), you figure out opacity based on the current time. You don't have to worry about delaying between frames, because requestAnimationFrame handles that for us. You stagger the transitions by offsetting the time. In this example, opacity1 depends on currTime and opacity2 depends on currTime minus a constant FADE_SPACING, so the opacity change for element 2 will start later than the opacity change for element 1 by FADE_SPACING ms.
The JSFiddle has all the details filled in. It animates the opacity of two <h1> elements, with a spacing between the beginning of the animation for each element. I hope this helps!
I am trying to display several count down timers on same page. now as far as i know there are 2 ways of doing it without using jquery plugins or some other scripts (if you know of a good one please let me know)
starting 1 sec setInterval and a global variable that will contain milliseconds and then just reduce -1000 every interval.
creating a function that reduce 1 sec from a global variable and then at the bottom of that function setting a setTimeout of 1 sec that will run that functions so basically recursion every 1 sec.
My question is which of the 2 options will work better and/or faster?
here is demonstrative code for both:
setInterval:
var amount_of_seconds_left = 46800000;
setInterval(function(){
if(amount_of_seconds_left > 1000){
amount_of_seconds_left -= 1000;
}
},1000);
setTimeout:
var amount_of_seconds_left = 46800000;
function startTime(){
if(amount_of_seconds_left > 1000){
amount_of_seconds_left -= 1000;
t=setTimeout(function(){startTime()},1000);
}
}
Both ways could work but i was wondering performance wise which is better and is performance is even an issue with this ?
setInterval and setTimeout don't start after 1000ms e.g. if another script is running, so both can cause delays. It would be better to use the setIntervall to call the display update only and use the the Date object to calculate the exactly remaining time. E.g. after the browser was busy the timer shows the correct time after the next update.
Here an example:
HTML:
<div id="timer1"></div>
<div id="timer2"></div>
javascript:
// update all timer
function updateTimer() {
for (var i in aTimer) {
var oTimer = document.getElementById(aTimer[i].sId);
var iSeconds = parseInt((aTimer[i].iFinished - Date.now()) / 1000);
oTimer.innerHTML = iSeconds;
}
}
// Init all timers with DOM-id and finish time
var aTimer = [
{ sId: 'timer1', iFinished: Date.now() + 46800000 },
{ sId: 'timer2', iFinished: Date.now() + 780000}
];
// call display update
setInterval(function() {
updateTimer();
}, 333);
I belive that the setInterval code executes every 1000ms exactly, while the setTimeout waits 1000ms, runs the function, which takes some ms, then sets another timeout. So the wait period is actually greater than 1000ms.
From this post:
setTimeout or setInterval?
So, maybe total brainfart here. The syntax for setInterval() is pretty clear. Do something every x miliseconds. How is this best translated to using the requestAnimationFrame() ?
I have about 300 objects and each is supposed to perform an animation sequence at a certain interval (every 8, 6, 2, etc seconds)? How can I best accomplish this using requestAnimationFrame() which gets called ~60 times a second? There is probably an easy answer, I just, for the life of me, can't figure it out.
To force requestAnimationFrame to stick to a specific FPS you can use both at once!
var fps = 15;
function draw() {
setTimeout(function() {
requestAnimationFrame(draw);
// Drawing code goes here
}, 1000 / fps);
}
A little weird, but noth the most confusing thing in the world.
You can also use requestAnimationFrame not with FPS but with elapsed time in order to draw objects that need to be updated based on the time difference since the last call:
var time;
function draw() {
requestAnimationFrame(draw);
var now = new Date().getTime(),
dt = now - (time || now);
time = now;
// Drawing code goes here... for example updating an 'x' position:
this.x += 10 * dt; // Increase 'x' by 10 units per millisecond
}
These two snippets are from this fine article, which contains additional details.
Good question by the way! I don't think I've seen this answered on SO either (and I'm here way too much)
requestAnimationFrame is pretty low level, it just does what you already said: roughly gets called at 60fps (assuming the browser can keep up with that pace). So typically you would need to build something on top of that, much like a game engine that has a game loop.
In my game engine, I have this (paraphased/simplified here):
window.requestAnimationFrame(this._doFrame);
...
_doFrame: function(timestamp) {
var delta = timestamp - (this._lastTimestamp || timestamp);
for(var i = 0, len = this.elements.length; i < len; ++i) {
this.elements[i].update(delta);
}
this._lastTimestamp = timestamp;
// I used underscore.js's 'bindAll' to make _doFrame always
// get called against my game engine object
window.requestAnimationFrame(this._doFrame);
}
Then each element in my game engine knows how to update themselves. In your case each element that should update every 2, 6, 8 seconds needs to keep track of how much time has passed and update accordingly:
update: function(delta) {
this.elapsed += delta;
// has 8 seconds passed?
if(this.elapsed >= 8000) {
this.elapsed -= 8000; // reset the elapsed counter
this.doMyUpdate(); // whatever it should be
}
}
The Canvas API along with requestAnimationFrame are rather low level, they are the building blocks for things like animation and game engines. If possible I'd try to use an existing one like cocos2d-js or whatever else is out there these days.
I have a scenario where one client PC will be driving multiple LCD displays, each showing a single browser window. These browser windows show different data which is on an animated cycle, using jquery.
I need to ensure that both browsers can be synched to rotate at exactly the same time, otherwise they'll animate at different times.
So my question is - can I trigger jquery to alternate the content based on the local PC clock?
eg each time the clock seconds == 0, show version 1, each time clock seconds == 30, show version 2 etc?
This is (in my experience) the most precise way of getting timers to trigger as closely as possible to a clock time:
// get current time in msecs to nearest 30 seconds
var msecs = new Date().getTime() % 30000;
// wait until the timeout
setTimeout(callback, 30000 - msecs);
Then, in the callback, once everything is done, do the same again to trigger the next event.
Using setInterval causes other problems, including clock drift. The calculation based on the current time accounts for the time executing the callback itself.
You'll still also need to use Date().getTime() as well to figure out which frame of your animation to show.
The whole thing would look something like this:
function redraw() {
var interval = 30000;
// work out current frame number
var now = new Date().getTime();
var frame = Math.floor(now / interval) % 2; // 0 or 1
// do your stuff here
.. some time passes
// retrigger
now = new Date().getTime();
setTimeout(redraw, interval - (now % interval));
}
redraw();
working demo at http://jsfiddle.net/alnitak/JPu4R/
The answer is: yes you can.
Use Date.getTime() to monitor time
Trigger your js function every 30 seconds
You could do something like this.
This way, no matter when you launched the different browsers, their rotations would be in sync.
var t=setInterval("check()",1000);
function check(){
var d = new Date();
if(d.getSeconds() == 0)
{
alert('do something');
} else if (d.getSeconds() == 30)
{
alert('do something else');
}
}
Why not launch one window from the other - that way the parent window will have complete control over when the animation starts, because they are in the SAME PROCESS. No clocks required.
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();
}