I'm trying to learn Javascript animations but, as I thinked, it doesn't work xD.
UPDATED DELETING PASTING MISTAKES
I tried
function click_home()
{
for(i=0;i<=10;i++)
{
setTimeout(setOpacity("div_home",i),200);
}
};
function setOpacity(id,value) {
document.getElementById(id).style.opacity = value/10;
document.getElementById(id).style.filter = 'alpha(opacity=' + value*10 + ')';
};
And HTML:
<td id="button_home" onclick="click_home();"> Home </td>
But obviously is wrong.
What can I do to do this?:)
Thanks to all replies :)
Firstly, you've got a syntax error in the for loop (change the , to a ;).
You need to pass a function to setTimeout, so it can execute it periodically. What you're currently passing setTimeout is the result of executing setOpacity("div_home",i) (i.e. undefined).
function click_home()
{
for(i=0;i<=10;i++)
{
setTimeout(function () {
setOpacity("div_home",i)
}, 200);
}
};
What you'll also find is you value of i will always end up being the last value, because of the scope of i, to fix this you need to add a new scope level. For a more detailed description on this problem, see How to reference right value of `i` in a callback in a loop?
function click_home()
{
function delay(i) {
setTimeout(function () {
setOpacity("div_home",i)
}, 200);
}
for(i=0;i<=10;i++)
{
delay(i);
}
};
As noted in the comments, you will find all of your timeouts will execute after 200ms.. to get the animation you'll need to stagger the execution. The simplest way would be to add i to the delay calculation; i.e. 25 * i.
I'm not sure why you're setting the opacity and filter to 0 first in your setOpacity function; these resets will immediately be set to the values that follow.
You should also look at caching the result of document.getElementById(id).style, as you're looking it up 4 times (and will be 2 if you remove the unneeded resets described above).
function setOpacity(id,value) {
var style = document.getElementById(id).style;
style.opacity = value/10;
style.filter = 'alpha(opacity=' + value*10 + ')';
};
Related
I'm trying to build a loop function with a delay. I've found this solution here:
How do I add a delay in a JavaScript loop?
...but because I want to use the function several times, I want to pass variables to the function. This is proving difficult. For example, say I've got a function called "animate_block" that I want to call with variable "blah". This function itself calls another function with this variable, then moves the variable forward by 1 until it reaches some limit, with a recursive setTimeout so it doesn't all happen at once. Should it look something like:
animate_block(blah)
function animate_block(ab_blah){
move_block(ab_blah);
ab_blah = ab_blah +1
if(ab_blah <= 10){
setTimeout(animate_block(){ab_blah},30);
}
? And if it shouldn't which bit(s) have I got wrong?
Ta!
setTimeout takes a function as first argument. You can create a function on the fly, which has access to the ab_blah because functions have access to the parent scope.
animate_block(blah);
function animate_block(ab_blah) {
move_block(ab_blah);
ab_blah = ab_blah +1
if (ab_blah <= 10) {
setTimeout(function() { animate_block(ab_blah); }, 30);
}
}
Read this article on closures in JS to get a good understanding.
Here the limit is fixed. We use requestAnimationFrame instead of the 30ms timeout, as animate_block sounds like something visual.
function moveBlock(position) {
'use strict';
console.log('moving to block', position);
return;
}
function animateBlock(position) {
'use strict';
//... in case the call was made with `position` higher then 10 it should not be executed
if(position <= 10) {
moveBlock(position);
//we increase position, but `position++` returns `position` prior to the increment
if (position++ < 10) {
window.requestAnimationFrame(animateBlock.bind(animateBlock, position));
}
}
}
animateBlock(1);
Android < 4.4, IE8 and Opera Mini require a polyfill.
This is originally from (Pause execution in while loop locks browser (updated with fiddles))
I have been at this all day and I can't figure out how to keep javascript from advancing to the next line and in essence executing all lines at once. I have tried every combination of delay / setTimeout I can think of to no avail.
I just want the elements in the array to flash once then pause, then do it again for another element in the array till all elements have been removed and the array is empty.
But because javascript is executing all lines at once I end up with the appearance of all elements flashing at the same time.
Here is the fiddle:
http://jsfiddle.net/ramjet/xgz52/7/
and the relevant code:
FlashElement: function () {
while (elementArray.length) {
alert('a ' + elementArray.length);
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
alert('delay complete');
elementArray = elementArray.not($el);
alert('array popped');
alert('z ' + elementArray.length);
}
},
ANSWER FOR THIS SITUATION. Hopefully it will help others.
As Zach Saucier points out the loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code. As Zach pointed out and I read here (http://javascript.info/tutorial/settimeout-setinterval) while he was responding setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
The reason why you were having trouble is because setTimeout function is non-blocking and will return immediately. Therefore the loop will iterate very quickly, initiating each of the timeouts within milliseconds of each other instead of including the previous one's delay
As a result, you need to create a custom function that will wait on the setInterval to finish before running again
FlashElement: function () { // Call it where you had the function originally
myLoop();
},
...
function myLoop() {
setTimeout(function () { // call a setTimeout when the loop is called
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
if (0 < elementArray.length) { // if the counter < length, call the loop function
myLoop();
}
}, 1000)
}
Feel free to change the delay to whatever value you wish (3000ms to let each fade finish before the last at the moment). If you want to start the fade in of the next before the previous ends and keep them in their original positions you would have to animate the opacity using .css instead of using fadeIn and fadeOut
My answer is based on this answer from another SO question
I've written the code below to act as a simple slideshow in the header of a clients website. Now, the only problem is when I put it into a for or a while loop it gets to the end of the first loop and then stops.
I've tried using calling togglinga() in the last callback, I've tried, wrapping the whole thing in a for and while loop, I've tried creating a different function that calls this one and then using that same function name in the final call back but get the same result everytime. I'd really appreciate someone casting their eye over this to see if they can see anything I can't....
function triggerAnimation(){
$("#logoareacontainer").delay(15000).fadeOut(3000, function() {
$("#title-1").fadeIn(0).delay(0, function() {
$("#cdsl-1-1").fadeIn(1000).delay(2000).fadeOut(0, function(){
$("#cdsl-1-2").fadeIn(0).delay(2000).fadeOut(0, function(){
$("#logoareacontainer").fadeIn(1000).css({display:'block'})
})
})
})
})
}
Much shorter and easier if you break this into functions that can be called in a cyclical manner.
Note that .delay() doesn't accept a callback function, which was a big part of the problem.
Here's a demo: http://jsfiddle.net/kjaHZ/
// for each "title-", keep track of how many "cdsl-"s there are
var titles = [null, 4, 2, 2, 2, 1, 1];
start();
// start it off
function start() {
$("#logoareacontainer").delay(1500).fadeOut(3000, function () {
doTitle(1);
});
}
// this starts a "title-" section
function doTitle(i) {
if (i < titles.length) {
// do the "title-" for the given "i" variable
$("#title-" + i).fadeIn(0, function () {
// after fading in, do the "cdsl-" ids
doCDSL(i, 1);
});
} else {
// or if "i" is >= titles.length, we're done
$("#logoareacontainer").fadeIn(1000).css({display:'block'});
}
}
// this starts a "cdsl-" section
function doCDSL(i, j) {
$("#cdsl-" + i + "-" + j).fadeIn(1000)
.delay(2000)
.fadeOut(0, function () {
if (j < titles[i]) {
// move to the next "cdsl-"
doCDSL(i, j+1);
} else {
// or do the next "title-"
$("#title-" + i).fadeOut(1000).css({display:'none'})
doTitle(i+1);
}
})
}
although your code is pretty awfull here u are :) u missed ()
function togglinga(){ triggerAnimation(); };
You can't put the code in a loop, because it is asynchronous. The loop would just start all the animations at once, because the outermost call won't wait until all the animations are complete.
At the innermost level, just call triggerAnimation to make it restart.
I have a recursive type function in Javascript that runs like this:
function loadThumb(thumb) {
rotate=setTimeout(function() {
loadThumb(next);
}, delay);
}
Note: I've simplified the function to make it easier to read.
I have "a" tags called like this
Load thumb 3
However, they don't clearout the timer, the timer continues to cycle through the function irregardless of the clearTimeout() being called.
Any ideas why? I think it might have something to do with a scope problem or something like that.
Yeah, you need to make rotate a global variable. Simply declare it outside the function like so:
var rotate;
var delay = 1000;
function loadThumb(thumb) {
alert("loading thumb: " + thumb);
rotate = setTimeout(function() {
loadThumb(thumb + 1);
}, delay);
}
Also, you need to make sure you clear the timeout before you call loadThumb. Otherwise you'll clear the timer you just started.
Load thumb 3
fiddle: http://jsfiddle.net/63FUD/
it may be the issue of scope so make rotate as global variable and call clearTimeout(rotate);
refer clearTimeout() example
It may be a scoping issue if you are not declaring rotate externally.
Try this:
var rotate = 0;
function loadThumb(thumb) {
rotate=setTimeout(function() {
loadThumb(next);
}, delay);
}
Return false on the link
Since you are not using var rotate, it should not be a scoping issue since rotate would be in the window scope. Can you show the complete code?
It is considered poor coding to inline the script - you should attach the event handler onload of the page
Also you should not have the setTimeout inside a function that might be called for one image
Try this:
var rotate,next=1;
function loadThumb(thumb) {
if (thumb) ... use thumb
else ... use next
}
function slide() {
rotate=setInterval(function() {
loadThumb();
next++;
if (next>=images.length) next=0;
}, delay);
}
window.onload=function() {
var links = document.getElementsByTagName("a");
if (links[i].className==="thumbLink") {
links[i].onclick=function() {
var idx = this.id.replace("link","");
loadThumb(idx);
clearInterval(rotate);
return false;
}
}
document.getElementById("start").onclick=function() {
slide();
return false;
}
document.getElementById("stop").onclick=function() {
clearInterval(rotate);
return false;
}
slide();
}
assuming
Start
Stop
Show 1
Show 2
Show 3
If you have to manage multiple timeouts, you can use an object in the global scope and some custom methods to create and remove your timeouts. To access the methods you can either put the calls in the onclick handler of your links (like in the example), or use a library like jQuery to bind them.
<script type="text/javascript">
var timeouts = timeouts || {};
function createTimeout(name, milliseconds, callback) {
timeouts.name = setTimeout(callback, milliseconds);
}
function removeTimeout(name) {
if (typeof(timeouts.name) !== undefined) {
clearTimeout(timeouts.name);
timeouts.name = undefined;
}
}
createTimeout('foo', 5000, function() {
alert('timeout')
});
</script>
i have also posted an example on jsFiddle http://jsfiddle.net/AGpzs/
I'm not sure what exactly you are doing, because as far as I can see you didn't post all the code, but this looks better for me:
function loadThumb(thumb) {
return setTimeout(function() {
loadThumb(next);
}, delay);
}
and then:
Load thumb 3
I'm trying to write some JS replicating jQuery's fadeIn and fadeOut functions. Here's the code I have so far:
function fadeIn(elem, d, callback)
{
var duration = d || 1000;
var steps = Math.floor(duration / 50);
setOpacity(elem,0);
elem.style.display = '';
for (var i = 1; i <= steps; i++)
{
console.log(i/steps + ', ' + (i/steps) * duration);
setTimeout('setOpacity("elem", '+(i / steps)+' )', (i/steps) * duration);
}
if (callback)
setTimeout(callback,d);
}
function setOpacity(elem, level)
{
console.log(elem);
return;
elem.style.opacity = level;
elem.style.MozOpacity = level;
elem.style.KhtmlOpacity = level;
elem.style.filter = "alpha(opacity=" + (level * 100) + ");";
}
I'm having troubles with the first setTimeout call - I need to pass the object 'elem' (which is a DOM element) to the function setOpacity. Passing the 'level' variable works just fine... however, I'm getting "elem is not defined" errors. I think that's because by the time any of the setOpacity calls actually run, the initial fadeIn function has finished and so the variable elem no longer exists.
To mitigate this, I tried another approach:
setTimeout(function() { setOpacity(elem, (i / steps));}, (i/steps) * duration);
The trouble now is that when the function is called, (i/steps) is now always 1.05 instead of incrementing from 0 to 1.
How can I pass the object in question to setOpacity while properly stepping up the opacity level?
Your "another approach" is correct, this is how it's usually done.
And as for the problem of i always being a constant, that's how closures work!
You see, when you create this function that does something with i (like function() { alert(i); }), that function, as they say, 'captures', or 'binds' the variable i, so that variable i does not die after the loop is finished, but continues to live on and is still referenced from that function.
To demonstrate this concept, consider the following code:
var i = 5;
var fn = function() { alert(i); };
fn(); // displays "5"
i = 6;
fn(); // displays "6"
When it is written in this way, the concept becomes a bit more evident, doesn't it? Since you're changing the variable in the loop, after the loop is finished the variable retains it's last value of (1+steps) - and that's exactly what your function sees when it starts executing.
To work around this, you have to create another function that will return a function. Yes, I know, kind of mind-blowing, but bear with me. Consider the revised version of my example:
function createFn( theArgument )
{
return function() { alert( theArgument ); };
}
var i = 5;
var fn = createFn( i );
fn(); // displays "5"
i = 6;
fn(); // still displays "5". Voila!
This works, because the fn function no longer binds the variable i. Instead, now it binds another variable - theArgument, which has nothing to do with i, other than they have the same value at the moment of calling createFn. Now you can change your i all you want - theArgument will be invincible.
Applying this to your code, here's how you should modify it:
function createTimeoutHandler( elemArg, iDivStepsArg )
{
return function() { setOpacity( elemArg, iDivStepsArg ); };
}
for (var i = 1; i <= steps; i++)
{
console.log(i/steps + ', ' + (i/steps) * duration);
setTimeout( createTimeoutHandler( elem, i/steps ), (i/steps) * duration);
}
Your first approach is evaluating code at runtime. You are most likely right about why it's failing (elem is not in the scope in which the code is eval'd). Using any form of eval() (and setTimeout(string, ...) is a form of eval()) is a general bad idea in Javascript, it's much better to create a function as in your second approach.
To understand why your second approach is failing you need to understand scopes and specifically closures. When you create that function, it grabs a reference to the i variable from the fadeIn function's scope.
When you later run the function, it uses that reference to refer back to the i from fadeIn's scope. By the time this happens however, the loop is over so you'll forever just get i being whatever it was when that loop ended.
What you should do is re-engineer it so that instead of creating many setTimeouts at once (which is inefficient) you instead tell your setTimeout callback function to set the next Timeout (or you could use setInterval) and do the incrementing if your values inside that callback function.