Worn out getting animation to sequence - javascript

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

Related

Array.pop() Isn't killing my setInterval

I've written some code that when you click a button it adds an instance of a function to an array,
var objects = [];
$(document).on("click", ".addButton", function(){
objects.push(new newObject(1));
});
function newObject(amount){
setInterval(function(){
addValue(amount);
}, 1000);
}
So then every second each new object created keeps running the addValue function every second adding the amount.
The problem is when I try and destroy that function with objects.pop() it deletes the object but the setInterval doesn't stop running.
How do I make it destroy everything in that function and stop it from running?
There is nothing quite like that in JS for setInterval. I would suggesting declaring a method to handle clean up.
// "Class" declaration
function newObject(amount) {
var id = setInterval(function() {
addValue(amount);
}, 1000);
this.kill = function() {
clearInterval(id);
}
}
// "Public" api for the data structure
var objects = [];
function addNewObject() {
objects.push(new newObject(1));
}
function destroyLastObject() {
objects.pop().kill();
}
// Event bindings
$(document).on("click", ".addButton", addNewObject);
$(document).on("click", ".removeButton", destroyLastObject);
Completely untested, but along these lines should work.
EDIT
This, imo, is a great resource for learning about different patterns within javascript - long but well well worth the read: https://addyosmani.com/resources/essentialjsdesignpatterns/book/
You got to find something to check against to clear the interval. I am clearing based on array length. It only executes once.
// you got to find something to check against to clear the interval
var objects = [];
document.addEventListener("click", function(){
console.log('click');
objects.push(new newObject(1));
});
function newObject(amount){
var interval= setInterval(function(){
if(objects.length !==0){
clearInterval(interval);
}
}, 1000);
}

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.

SetTimeOut() in THREE JS

I realize a project for the master degree.
I must create a three js space invaders game.
The project is well underway but i have a problem. My aliens ( THREE.Mesh object ) must be able to fire randomly.
To carry out that, i've created a function which should draw a random number. This function works.
The problem comes the animate() function. In fact i can't put a SetTimeOut() the animate() function.
The SetTimeOut() works the first time animate() is called but after there is no timer. The code executing continually without waiting the timer.
Maybe the problem coming because animate is continually called by requestAnimationFrame();
My code :
Index.html =>
if (!init())animate();
function animate(){
requestAnimationFrame( animate );
level1.animate();
render();
}
Level.js =>
Level.prototype.animate = function()
{
//Timer doesn't work
var that = this;
//Just a test with a simple console.log test
setTimeout(function() { console.log("test"); },10000);*/
this.sky.rotation.x -=0.005;
this.spaceship.fire();
for (var i=0; i<this.ducks.length;i++)
{
this.ducks[i].move();
if (this.ducks[i].is_ready_to_fire())
this.ducks[i].fire_if_ready();
}
};
With this example the program will wait 10 seconds the first time before print "test" and after the first call, print "test" without waiting.
Have you any ideas ?
Thank you very much.
Sorry for my poor english.
The question if you need a timer for your purpose.
If I understand your problem correctly, you need the aliens to fire after a random amount of time.
If you don't care about the exact amount of time and only about the aliens shooting at random occasions, I'd use a counter on each alien to count the frames until it shoots.
So your code will look something like this:
var MAX_FRAMES_TO_WAIT = 600000;
Alien.prototype.init = function() {
this.framesUntilFire = Math.round(Math.random() * MAX_FRAMES_TO_WAIT);
}
Alien.prototype.fireWhenReady = function() {
if(--this.framesUntilFire === 0) {
this.fire();
this.framesUntilFire = Math.round(Math.random() * MAX_FRAMES_TO_WAIT);
}
}
function animate() {
requestAnimationFrame(animate);
/* ... */
for (var i=0; i<this.ducks.length;i++)
{
this.ducks[i].move();
this.ducks[i].fireWhenReady();
}
That should do the trick. Be aware that this will mean that the enemies fire quicker when the framerate is higher and slower when the framerate should drop.
You can counter that with counting the framerate as well and using it as a divider to level it out.
I hope that helped you a bit!
You can avoid setting a new timer every frame by simply resetting a new timer once the previous one has finished. A simple solution for this is recursive:
Level.prototype.fireDelayed = function() {
setTimeout(function() {
if (!this.currentLevel) {
return;
}
this.fireDelayed();
this.fire();
}.bind(this), Math.random() * 1000);
};
It simply stops firing if the level is no longer the currentLevel.
Does that make sense?

update graph with new data

I have a list of files that I want to plot in order (line graph), creating an animation as d3 transitions between them.
I have an update function that takes as input a file name, and transitions the current line graph to the new line graph. This is working fine.
However, I now want to transition in order between five different files.
Here is the code I am using:
var file_list = ["time1.csv", "time2.csv", "time3.csv", "time4.csv", "time5.csv"];
var num_files = file_list.length;
for (i = 0; i < num_files; ++i) {
setTimeout(setDelay(i), 1000);
}
function setDelay(i) {
setTimeout(function(){
update(file_list[i]);
}, 1000);
}
This does transition my line graph, but it transitions from the first (time1.csv) immediately to the last (time5.csv), skipping everything in the middle.
How can I figure out what is going wrong?
If I console.log(file_list[i]) in the loop, it is looping through and printing time1.csv ... time5.csv.
Thanks!
The setDelay is called on every iteration, to evaluate it on timeout you must pass the function object as callback setTimeout(function() { setDelay(i); }, 1000)
But your logic is wrong, why two setTimeout per iteration? All of them is calling immediatly after each iteration, you must chain the setDelay calls or multiply the delay by iteration index.
var file_list = ["time1.csv", "time2.csv", "time3.csv", "time4.csv", "time5.csv"];
function setDelay() {
var file = file_list.shift();
if (file) {
update(file);
setTimeout(setDelay, 1000);
}
}
setDelay();

Stopping .each execution on every iteration for a spesific time interval in jquery?

I am trying to create the following functionality in my javascript:
$("mySelector").each(function(){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
My question is how can I stop every iteration for the duration of the myFunction()?
No, that isnt possible. You'll have to code it differently, possibly with a setTimeout based on the current index of .each.
$("mySelector").each(function(i){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
setTimeout(myFunction,i*5000);
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
Edit: You can also do it with queuing: http://jsfiddle.net/9Bm9p/6/
$(document).ready(function () {
var divs = $(".test");
var queue = $("<div />");
divs.each(function(){
var _this = this;
queue.queue(function(next) {
myFunction.call(_this,next);
});
});
});
function myFunction(next) {
// do stuff
$(this).doSomething();
// simulate asynchronous event
var self = this;
setTimeout(function(){
console.log(self.id);
// go to next item in the queue
next();
},2000);
}
​
Here's a jsFiddle that I think will do what you need:
http://jsfiddle.net/9Bm9p/2/
You would just need to replace the selector with what you use.
The "loop" that is occurring will wait for myFunction to finish before moving on to the next element. I added the setTimeout inside of myFunction to simulate it taking a period of time. If you are using asynchronous things, such as an AJAX request, you would need to put the call to myFunction inside of the complete method...or in the callback of an animation.
But as someone already commented, if everything in myFunction is synchronous, you should be able to use it as you are. If you are looking for this process to be asynchronous, or if things in myFunction are asynchronous, you cannot use a for loop or .each().
(function () {
"use strict";
var step = 0;
var content = $("mySelector");
var max = content.length;
var speed = 5000; // ms
var handle = setInterval(function () {
step++;
if (step >= max) {
clearInterval(handle);
} else {
var item = content[step];
// do something
}
}, speed);
}());
setInterval will do it once-every-n-miliseconds, and clearInterval will stop it when you're done. This won't lock up the browser (provided your "do something" also doesn't). FRAGILE: it assumes that the results of $("mySelector") are valid for the duration of the task. If that isn't the case then inside do something then validate item again.

Categories