JavaScript timeline animation (setTimeout accuracy issues) - javascript

I began writing a simple animation class in JS, which utilizes Zepto.js animation capabilities but adds timeline-like capability to it.
The timeline itself is a simple array, that executes functions embedded in it when it's play() function is called:
play : function(callback){
for(var i=0; i<Animator.timeline.buffer.length; i++){
Animator.timeline.buffer[i].animation();
}
if(callback){
callback();
}
}
The setTimeout goes directly in the animation:
alpha : function(parameters, callback, delay){
var target = parameters.target;
var duration = parameters.duration;
var easing = parameters.easing;
var value = parameters.value;
if(delay){
setTimeout(function(){run();},delay*1000);
} else {
run();
}
function run(){
$(target).anim({opacity:value},duration,easing);
if(callback){
callback();
}
}
}
So basically, the timeline just runs the setTimeout-ed functions which are placed in it's buffer array.
This approach works (almost) as intended with WebKit animations, but i've run into a few problems when doing image sequences (animations using setInterval which change the image's src). As JS timers don't guarantee execution in their appointed time, sometimes animations run late, likely because of the embedded setInterval inside them.
Any ideas on how to solve this? I am aware that embedding all animations as callbacks inside one another would solve much of the issues, but i don't really know how to do that from inside the timeline loop. Also, it would quickly become an unreadable mess of callbacks if I call all functions in a direct manner (without using the timeline).
For reference, the sequence function of my animator class:
sequence : function(parameters, callback, delay){
var target = parameters.target;
var path = parameters.path;
var baseName = parameters.baseName;
var digits = parameters.digits;
var extension = parameters.extension;
var frames = parameters.frames;
var loop = parameters.loop;
if(parameters.interval){
var _interval = parameters.interval
} else {
var _interval = 15;
}
var currentFrame = 0;
var imageUrl = '';
var fileName = baseName;
for(var i=0; i<=digits; i++){
fileName+='0';
}
if(delay){
setTimeout(function(){runSequence();},delay*1000);
} else {
runSequence();
}
function runSequence(){
var interval = setInterval(function(){
if(currentFrame >= frames){
currentFrame = 0;
if(!loop) {
clearInterval(interval);
if(callback){
callback();
}
}
} else {
imageUrl = path+fileName.substring(0, fileName.length-currentFrame.toString().length)+currentFrame+"."+extension;
$(target).attr('src',imageUrl);
currentFrame++;
}
},_interval);
}
}
Also, a sample of an animation created by using this class:
Animator.timeline.append(function(){
Animator.alpha({'target':'#logo', 'value':1, 'duration':1, 'easing':'ease-out' });
});
Animator.timeline.append(function(){
Animator.sequence({'target':'#sequence', 'path':'images/sequences/index/', 'baseName':'nr1_', 'digits':3, 'extension':'png', 'frames':50},'',1.5);
});
Animator.timeline.append(function(){
Animator.scale({'target':'#text', 'width':.5, 'height':.15, 'duration':1, 'easing':'ease-in-out'},'',3.2);
});
Animator.timeline.append(function(){
Animator.alpha({'target':'#link', 'value':1, 'duration':1,'easing':'ease-out'},'',4.7);
});
Animator.timeline.play();
As an additional note, I was aiming to create something similar to GreenSock in AS3, if that helps.
Thanks.

Accurate setInterval can be simulated by compensating for the time it takes to execute every iteration, maybe this gist I wrote can help you:
https://gist.github.com/1185904

Related

User interaction conflict during SetTimeout in Javascript

I am working on a WordPress (Javascript) plugin that alters text fields based on user interaction with an HTML5 slider. One of its effects is to reveal a <span> string one character at a time using SetTimeout to create a delay (a few ms) so the effect is perceptible. I'm accomplishing this by getting the DOM element's contents and then rebuilding it one character at a time.
The problem is that since SetTimeout is aynsynchronous, the user can potentially move the slider faster than a single reveal loop can complete, resulting in half-empty DOM elements that never get corrected.
Is there a way to prevent this, or alternatively, a way to accomplish the task that avoids the conflict altogether? I have tried turning off the EventListener (for the HMTL5) at various points in the delay loop but cannot find a place that avoids the issue. The other possibility is to load all the <span> contents into arrays in order to retain intact copies of everything ... but something tells me there's a better way to do it that I don't know.
Here is example code. Initialize() is called when the HTML page involved loads.
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if (
// conditions
)
{ var cols = document.getElementsByClassName(attr+i);
RevealTextLines (cols);
}
// 'delay' is a global variable to set the delay length
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h = h + numchars;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
}, delay);
}
})(1);
});
}
The boolean flag suggested above does not work in this case, but it did inspire the following solution:
Provided the number of iterations are known in advance (which in this case they are), define a global counter variable outside the functions. Before the SetTimeout loop, set it to the number of iterations and decrease it by 1 every time through. Then have the calling function proceed only when the counter's value is zero.
var counter = 0;
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if ( counter == 0)
{ var cols = document.getElementsByClassName(classname);
RevealTextLines (cols);
}
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
timer = el.length;
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h++;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
timer--;
UpdateSlider();
}, delay);
}
})(1);
});
}
If anyone knows a more efficient solution, I would remain interested.

How to update a web page from javascript without exiting javascript

I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/

JavaScript animation is stuck, and will show only end result

I am trying to fiddle around and create a little fadeOut function in JavaScript,
This is what I came up with:
function fadeOut(id, time){
var elem = document.getElementById(id);
elem.style.opacity = 1;
var opc = 1;
while(opc >= (1/time)){
opc -= (1/time);
console.log(opc);
elem.style.opacity = opc;
}
elem.style.opacity = 0;
}
But this will not show the div's opacity in "real-time" but rather the end result, which is opacity = 0;
I've tested it out here:
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
It would take it a long time to calculate and only when it finishes it will dump the result,
not showing it seamlessly, while calculating,
How can I resolve this?
You need to set timers, as, until your function is done and the event handler can run, the UI won't be updated.
It is because you are not 'yielding` the thread so as to allow the browser to apply the changes.
Use setTimeout(..) instead like this:
function fadeOut(id, time){
var elem = document.getElementById(id);
if(opc >= (1/time)){
opc -= (1/time);
console.log(opc);
elem.style.opacity = opc;
setTimeout(function(){fadeOut(id,time);}, 100);
}
else
elem.style.opacity = 0;
}
Not really a great code but it gives you the idea.
May be you could use jQuery library. In that case, you will use fadeOut
one potential causing this problem can be bubbling of event.try to use event.stopPropagation() to prevent the event (in case that you are using the FadeOut function in response to an event) from bubbling up.
the code is
function StopBubble(e)
{
if (!e)
e = window.event;
e.cancelBubble = true; /* Microsoft */
if (e.stopPropagation)
e.stopPropagation(); /* W3C */
}
You have to specify a delay between every modification you make to the DOM in order to see the effect. There is no option in javascript to add a pause feature, so you have to rely on timers. There are two ways to achieve this. Most commonly used approach is setInterval(fn,sec). But setInterval is bad, because it executes the callback every interval of time specified regardless the previous execution is complete or not.
I personally suggest using a library rather than re-inventing the wheel. But its always good to have a basic understanding what the libraries do behind the scene.
Below is a sample snippet to achieve this without setInterval.
Note: This is just for demonstration. Not cross browser compatible. Extend it for reusability and cross broswer compatibility.
<div id="mydiv"></div>
function fadeOut() {
var _timeOut,
_proceed = true,
_elm = document.getElementById( "mydiv" ),
_opacity;
function addOpacity() {
// _elm.style.opacity will return empty string
// see https://developer.mozilla.org/en-US/docs/DOM/window.getComputedStyle
// getComputedStyle() not cross broswer
_opacity = +window.getComputedStyle( _elm, null ).getPropertyValue( "opacity" );
if( _opacity > 0.1 ) {
// a simple algorithm for basic animation
_elm.style.opacity = _opacity / 2;
} else {
_elm.style.opacity = 0;
_proceed = false;
}
}
(function animate() {
if( !_proceed ) {
clearTimeout( _timeOut );
return;
}
addOpacity();
// unlike setInterval(), this approach waits for addOpacity() to complete before its executed again
_timeOut = setTimeout( animate, 1000 );
}());
}
Why not to use a JavaScript library like jQuery? Because some JavaScript code is not compatible with different sorts of browsers.
In jQuery you can use this:
$(".id").css("background","blue").fadeOut("slow");

setInterval to loop through array in javascript?

I have a website where they want a news ticker. Currently, I have a array that populates it, and every x seconds, I want the news story to change.
function startNews(stories) {
}
I am aware that you can use setInterval, but it has to go through a new function and you can't specify certain javascript in the same function to fire when it does.
What are you suggestions?
Thanks!
You should use either setInterval() or repeated calls to setTimeout(). That's how you do something in javascript at some time in the future.
There are no limitations on what you can do with either of those timer functions. What exactly do you think you cannot do that is making you try to avoid them?
Here's a pseudo code example:
var newsArray = []; // your code puts strings into this array
var curNewsIndex = -1;
var intervalID = setInterval(function() {
++curNewsIndex;
if (curNewsIndex >= newsArray.length) {
curNewsIndex = 0;
}
setTickerNews(newsArray[curNewsIndex]); // set new news item into the ticker
}, 5000);
or it could be done like this:
var newsArray = []; // your code puts strings into this array
var curNewsIndex = -1;
function advanceNewsItem() {
++curNewsIndex;
if (curNewsIndex >= newsArray.length) {
curNewsIndex = 0;
}
setTickerNews(newsArray[curNewsIndex]); // set new news item into the ticker
}
var intervalID = setInterval(advanceNewsItem, 5000);
You should whenever possible use setTimeout. If your function takes longer to run than the interval, you can run into a constant 100% cpu usage situation.
Try this code:
http://jsfiddle.net/wdARC/
var stories = ['Story1','Story2','Story3'],
i = -1;
(function f(){
i = (i + 1) % stories.length;
document.write(stories[ i ] + '<br/>');
setTimeout(f, 5000);
})();
Replace document.write with your function.

How can I show a list of every thread running spawned by setTimeout/setInterval

I want to do this either by pure javascript or any sort of console in a browser or whatever.
Is it possible?
Thanks
Further explanations:
I want to debug a library that does animations. I want to know if there's multiple timers created if there are multiple objects being animated.
Note that setTimeout() does not spawn new threads. Browser side scripting is not only single threaded, but the JavaScript evaluation shares the same single thread with the page rendering (Web Workers apart).
Further reading:
How JavaScript Timers Work by John Resig
You may want to build a timer manager yourself:
var timerManager = (function () {
var timers = [];
return {
addTimer: function (callback, timeout) {
var timer, that = this;
timer = setTimeout(function () {
that.removeTimer(timer);
callback();
}, timeout);
timers.push(timer);
return timer;
},
removeTimer: function (timer) {
clearTimeout(timer);
timers.splice(timers.indexOf(timer), 1);
},
getTimers: function () {
return timers;
}
};
})();
Then use it as follows:
var t1 = timerManager.addTimer(function () {
console.log('Timer t1 triggered after 1 second');
}, 1000);
var t2 = timerManager.addTimer(function () {
console.log('Timer t2 triggered after 5 second');
console.log('Number of Timers at End: ' + timerManager.getTimers().length);
}, 5000);
console.log('Number of Timers at Start: ' + timerManager.getTimers().length);
The above will display the following result in the console:
// Number of Timers at Start: 2
// Timer t1 triggered after 1 second
// Timer t2 triggered after 5 second
// Number of Timers at End: 0
Note that the timerManager implementation above uses the Array.indexOf() method. This has been added in JavaScript 1.6 and therefore not implemented by all browsers. However, you can easily add the method yourself by adding the implementation from this Mozilla Dev Center article.
Finally done, it was interesting for me so I spent some time trying to come up with something, and here it's
It overrides browser's setTimeout and fill active status of current active calls in window._activeSetTimeouts hash, with window._showCurrentSetTimeouts() demo function that displays current setTimeout calls that are waiting.
if(typeof window._setTimeout =='undefined') {
window._setTimeout=window.setTimeout;
window._activeSetTimeouts={};
window._activeSetTimeoutsTotal=0;
window._setTimeoutCounter=0;
window._showCurrentSetTimeouts=function() {
var tgt=document.getElementById('_settimtouts');
if(!tgt) {
tgt=document.createElement('UL');
tgt.style.position='absolute';
tgt.style.border='1px solid #999';
tgt.style.background='#EEE';
tgt.style.width='90%';
tgt.style.height='500px';
tgt.style.overflow='auto';
tgt.id='_settimtouts';
document.body.appendChild(tgt);
}
tgt.innerHTML='';
var counter=0;
for(var i in window._activeSetTimeouts) {
var li=document.createElement('LI');
li.innerHTML='[{status}] {delay} ({calltime})<br /><pre style="width: 100%; height: 5em; overflow: auto; background: {bgcolor}">{cb}</pre>'.f(window._activeSetTimeouts[i]);
li.style.background=(counter++%2)?'#CCC' : '#EEB';
tgt.appendChild(li);
}
}
window.setTimeout=function(cb, delay) {
var id = window._setTimeoutCounter++;
var handleId = window._setTimeout(function() {
window._activeSetTimeouts[id].status='exec';
cb();
delete window._activeSetTimeouts[id];
window._activeSetTimeoutsTotal--;
}, delay);
window._activeSetTimeouts[id]={
calltime:new Date(),
delay:delay,
cb:cb,
status:'wait'
};
window._activeSetTimeoutsTotal++;
return id;
}
//the following function is for easy formatting
String.prototype.f=function(obj) {
var newStr=this+'';
if(arguments.length==1) {
if(typeof(obj)=='string') {
obj={x:obj};
}
for(var i in obj) {
newStr=newStr.replace(new RegExp('{'+i+'}', 'g'), obj[i]+'');
}
newStr+='';
} else {
for(var i=0; i<arguments.length; i++) {
newStr=newStr.replace('{'+(i+1)+'}', arguments[i]);
}
}
return newStr;
}
}
//following line for test
for(var i=0; i<5; i++) setTimeout(window._showCurrentSetTimeouts, 3000*i);
As others have mentioned, setTimeout doesn’t spawn a thread. If you want a list of all the timeout ids (so you can cancel them, for example) then see below:
I don’t think you can get a list of all timeout ids without changing the code when they are called. setTimeout returns an id—and if you ignore it, then it's inaccessible to your JavaScript. (Obviously the interpreter has access to it, but your code doesn't.)
If you could change the code you could do this:
var timeoutId = [];
timeoutId.push(setTimeout(myfunc, 100));
…Making sure that timeoutId is declared in global scope (perhaps by using window.timeoutId = []).
Just off the top of my head, but to reimplement setTimeout you’d have to do something like this:
var oldSetTimeout = setTimeout;
setTimeout = function (func, delay) {
timeoutId.push(oldSetTimeout(func, delay));
}
This isn’t tested, but it gives you a starting point. Good idea, molf!
Edit: aularon's answer gives a much more thorough implementation of the above idea.

Categories