Is it possible to pause a while loop until an animation finishes? - javascript

I'm trying to run a while loop that contains an animation. What I'd like to happen is for the while loop to pause, let the animation finish, then resume.
This is not my actual code, but it gets to the issue, I believe:
var counter = 0;
while (counter < 2) {
$(".one").animate({"left":"+=50px"}, "slow", function() {
counter++;
});
};
This crashes my browser because it doesn't wait for the animation to finish (and consequently it doesn't wait for the counter to increase) before it continues through the while loop. Thanks in advance!
https://jsfiddle.net/uhmctey6/
EDIT
Thanks everyone for explaining why this is impossible. I'm still unsure how to do what I need, however, and since I didn't use my actual code for the example, I'm not sure if the suggested solutions could help.
Here is what I'm actually trying to do: I'm making a turing machine with a reader and a array of cells that it reads. Upon running this function, I'd like to search through a list of turing code lines to see if one matches the readers current state and the content of the current cell that the reader is scanning. If there's a match, I'd like for the reader to make a series of changes specified by the relevant turing code line, then visually move over to the next cell, and only after this animation has completed start the process over again by searching through the list of turing code lines to see if there is a match for the reader's new state, etc.
I understand that this can't be achieved as I have it, using a while loop, but is there a way to do something like this another way? Thanks a lot!
var run_program = function() {
while (true) {
for (var j=0; j< program.length; j++) { //loops through the different lines of turingcode
if (reader.state === program[j].state && reader.scanning === program[j].scanning) { //if there is a line of turingcode for the readers current state and scanning values.
cellArray[reader.location].content = program[j].print; //change the content of the current cell to the new value
reader.location += 1; //increase the value of the reader's location
$(".reader").animate({"left":"+=50px"}, "slow"); //move the div to the right so it appears to be under the next cell
reader.scanning = cellArray[reader.location].content; //update the readers scanning value to be the value of the new cell
reader.state = program[j].next_state; // update the state of the reader to that specified by the line of turingcode
break;
}
else if (j === $scope.program.length-1) { //if there is no line of turingcode for the readers current state and scanning values, and j has looped through the entire list of turingcode lines
return; //halt the function
}
}
}
}

A for-loop/while-loop can run, skip and jump - but not stand still.
In other words, you cannot run asynchronous code affectively inside a synchronous loop. See this question/answer for more on that.
In the meantime, it looks like you want to run an animation a couple of times in sequence. I believe that jQuery can queue effects like animate, so it could be as simple as chaining the two calls to animate:
$(".one")
.animate({"left":"+=50px"}, "slow")
.animate({"left":"+=50px"}, "slow"); /* animate twice */
.one {
position: absolute;
width: 50px;
height: 50px;
background-color: green;
left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="one"></div>
Update
In response to your edit, it seems like what you want to do is turn a heavily synchronous piece of code into one that can accommodate some occasionally asynchronous behaviours.
Unfortunately, your example is unworkable for me because it has no data and context. However, I've given it a stab. Below is, untested, how I would turn your code into something as you've described in the edit of your question. I hope it helps.
var run_program = function() {
next(0);
// read a particular iteration
function next (j) {
if (j < program.length) {
// if there is a match, stop iterating and run a method which calls next when it's finished
if (reader.state === program[j].state && reader.scanning === program[j].scanning) {
turing(j);
} else {
// if there is no turing code then next is called instantly
next(j + 1);
}
}
}
// process a line of turing code
function turing (j) {
cellArray[reader.location].content = program[j].print;
reader.location += 1;
reader.scanning = cellArray[reader.location].content;
reader.state = program[j].next_state;
// may as well run this last as it is asynchronous
$(".reader").animate({"left":"+=50px"}, "slow", function () {
// next is called when the animation is finished
next(j + 1);
});
}
}

This won't work for the reasons stated in the other answers. An alternative option is to use conditional recursion as such:
var counter = 0;
function myAnimation () {
$(".one").animate({"left":"+=50px"}, "slow", function () {
counter++;
if (counter < 2) {
myAnimation();
}
});
}

Related

Why are old variables reused when using event handler functions with Javascript?

I'm trying to create a Simon game (project from a Udemy course) where you have to memorize a pattern sequence and click the right buttons. At each level, the pattern sequence increases by one. You have to repeat the entire sequence each time. If you lose, you have to press a key to restart the game. Here is an example:
https://londonappbrewery.github.io/Simon-Game/
However, when my game is restarted with my current code, everything falls apart. I don't know if old variables from a previous game continue into the new one, or maybe my code is just written really poorly.
I've implemented the entire game within a keypress event handler. Doing this initiates all the functions and variables of the game. The sequence is stored within an array, and each subsequent click on a box compares its colour value against the array. If it doesn't match - its game over. If it does match, it will either wait for you to finish the sequence correctly, or go to the next level.
Can anyone give me pointers on what I might be doing wrong? And whether it's at all possible to make this work the way I have it set up?
$(document).on("keypress", function(){
let counter = 0;
let level = 1;
let colours = ["green","red","yellow","blue"];
let nodeList = [];
function nextNode(){
randomNumberGen = Math.floor(Math.random()*4);
currentNode = colours[randomNumberGen];
nodeList.push(currentNode);
$("."+currentNode).fadeOut(200).fadeIn(200);
}
$("h1").text("Level "+level);
setTimeout(function() {
nextNode();
},500);
$(document).on("click", ".box", function(){
selectedNode = $(this).attr("id");
$("#"+selectedNode).addClass("pressed");
setTimeout(function(){
$("#"+selectedNode).removeClass("pressed");
},100);
if (nodeList[counter] != selectedNode){
gameSounds("wrong");
$("body").addClass("game-over");
setTimeout(function(){
$("body").removeClass("game-over");
},100);
$("h1").text("Game Over, Press Any Key to Restart");
}
else{
gameSounds(selectedNode);
counter++;
if (counter>= nodeList.length){
level++;
setTimeout(function(){
$("h1").text("Level "+(level));
}, 1000);
counter = 0;
setTimeout(function() {
nextNode();
},1000);
}
}
});
});
function gameSounds(key){
var soundPlay = new Audio("sounds/"+key+".mp3");
soundPlay.play();
}

How reduce delay in javascript / jquery change event?

I have five filters (multiple selects), which filter the data behind five visualisations in a dashboard. The filters have a JQuery change event, but the first line of code in this event takes half a second to happen. I don't understand why there's a delay. If I remove the code after the first line of code, the delay goes away. It's as though the code is not running in sequence.
The purpose of that first line of code is to make visible five "misty" (semi opaque) divs to obscure the graphics until the update code has run.
I'm using chosen.js on the selects but even when I remove chosen, there is still a delay. The filters are built dynamically. Here's the code that adds the change event:
for (i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change',function(e,p){
d3.selectAll("div.misty").style("visibility","visible");//make the fade divs appear - takes half a second
if (!p) {
for (var j=0; j<filters[0].length; j++) { filters[6][j] = []; filters[5][j].filterAll(); }
} else {
if (p.selected) {
var tempIndex = filters[0].indexOf(e.target.id);//whether it's company, portfolio, industry or country
filters[6][tempIndex].push(p.selected);//store this filter
filters[5][tempIndex].filterFunction(function(d){ return filters[6][tempIndex].indexOf(d)!=-1; });
}
if (p.deselected) {
var tempIndex = filters[0].indexOf(e.target.id);//whether it's company, portfolio, industry or country
var tempIndex2 = filters[6][tempIndex].indexOf(String(p.deselected));
filters[6][tempIndex].splice(tempIndex2,1);
filters[5][tempIndex].filterAll();
if (filters[6][tempIndex].length>0) { filters[5][tempIndex].filterFunction(function(d){ return filters[6][tempIndex].indexOf(d)!=-1; }); }
window.portfolio_p = window.portfolio_p2;
}
}
update();
})
}
If I remove the update commands, the code runs much quicker:
for (i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change',function(e,p){
d3.selectAll("div.misty").style("visibility","visible");//make the fade divs appear - takes half a second
}
Mmm, I have to agree you have an odd bug.
All I can suggest is pushing the filter manipulation into a later event thread.
You could contrive to use a Promise but window.setTimeout() is less expensive.
for(i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change', function(e, p) {
d3.selectAll('div.misty').style('visibility', 'visible');
window.setTimeout(function() {
if (!p) {
// etc...
} else {
// etc...
}
update();
}, 0); // in practice the delay will be something like 15 ms.
})
}

How to be frugal with JS DOM calls for onscroll functions

What I'm trying to achieve:
If user has scrolled more than 24px from the top (origin), do something once.
If the user scrolls back within 24px of the top (origin), reverse that something once.
I have this code:
$("#box").scroll(function(){
var ofs = $(".title").offset().top;
if (ofs <= 24)
// Do something
else
// Reverse that something
})
As I understand it, this function runs every time the user scrolls, which can result in hundreds of calls to the DOM.
This isn't every resource efficient - Is there a more passive approach to this?
Both conditions are triggered repeatedly even for small scroll amounts - any way to execute the code just once and not repeat if the same condition is true?
What you are looking to do is either throttling the requests or something called "debounce". Throttling only allows a certain number of calls to whatever in a period of time, debounce only calls the function once a certain time after action has stopped.
This is a good link explaining it: https://css-tricks.com/the-difference-between-throttling-and-debouncing/
There are several libraries out there that will do this for you like Underscore and Lodash. You can roll your own as well and the premise is basically the following for debounce:
var timer;
$('#box').scroll(function(){
//cancel and overwrite timer if it exists already
// set timer to execute doWork after x ms
})
function doWork(){
//do stuff
}
You can also look into using requestAnimationFrame depending on browser support. requestAnimationFrame example and it looks like it's supported in most modern browsers and IE >= 10
In the code below, everytime the user scrolls above or below that 25px threshold, one of the conditions in the if ($boxAboveBelow) if-statement will be called.
var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
var newAboveBelow = $box.scrollTop() < 25;
if (newAboveBelow !== $boxAboveBelow) {
$boxAboveBelow = newAboveBelow;
if ($boxAboveBelow) {
// If the user scrolls back within 24px of the top (origin), reverse that something once.
} else {
// If user has scrolled more than 24px from the top (origin), do something once.
}
}
})
If you need those to only be called once ever, you can set Boolean variables to record if those conditions have ever been called.
var aboveCalled = false;
var belowCalled = false;
var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
var newAboveBelow = $box.scrollTop() < 25;
if (newAboveBelow !== $boxAboveBelow) {
$boxAboveBelow = newAboveBelow;
if ($boxAboveBelow) {
!aboveCalled && doScrollAboveStuff();
aboveCalled = true;
} else {
!belowCalled && doScrollBelowStuff();
belowCalled = true;
}
if (aboveCalled && belowCalled) {
$box.off('scroll'); // No need to keep listening, since both called
}
});

jQuery image slider - weird behavior

I am creating an image slider for a WordPress site. It works only partialy, it presents the "show" and "hide" behaviour as expected but it does not switch the "background-image" CSS property of a div.
Links to images are in an array, a variable counter switches between all indexes of that array. Using "alert()" I was able to determine that it works perfectly well (all actions are performed correctly), but appears to bee working much too fast and causing an overflow. I am not sure if it performs actions in a correct order every time it runs though. Could you help me please?
That is the error I got from the console:
Uncaught RangeError: Maximum call stack size exceeded
And this is my code for the slider:
jQuery( document ).ready(function($) {
var imgArray = [];
imgArray.push("url(/projekty/sklep/wp-content/themes/maxshop/images/rotate/drugi.jpg)");
imgArray.push("url(/projekty/sklep/wp-content/themes/maxshop/images/rotate/trzeci.jpg)");
imgArray.push("url(/projekty/sklep/wp-content/themes/maxshop/images/rotate/czwarty.jpg)");
imgArray.push("url(/projekty/sklep/wp-content/themes/maxshop/images/rotate/piaty.jpg)");
var i = 0;
myWay(imgArray, i);
function myWay(theArray, number) {
$('#left-title-bar').show(500).delay(7000);
$('#left-title-bar').css("background",""+ theArray[number] + "");
$('#left-title-bar').hide(500);
if (number==3) {
number = 0;
} else {
number = number+1;
}
myWay(theArray, number);
}
});
your problem is that you use recursion without a condition to terminate ,
in your case you shouldn't be using recursion at all try using setInterval(functionName, milliseconds) instead.
setInterval will call functionName once every milliseconds
function myWay() { // make n and theArray in the outer scope of the function
$('#left-title-bar').hide(500,function (){
$('#left-title-bar').css("background",""+ theArray[number] + "");
$('#left-title-bar').show(500);
if (number==3) {
number = 0;
} else {
number = number+1;
}
}); //delay will not pause the code
}
setInterval(myway,2000) //will run myway once each 2 seconds
Edit : wait for the picture to change before changing it

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

Categories

Resources