Reputation: 1015
I've been tasked with rewriting this terrible piece of code which is meant to sequentially fade in layers on a map (they are all transparent pngs) on a web page. It needs to operate in a sequence, then loop back to the start where no layers are visible, and fade back in one at a time. This sequence should repeat itself forever.
I'm not really sure what the most recommended way of doing this is in javascript and interested what stack overflow has to say.
There has to be a better way than this! Am interested in pros/cons of any methods.
setInterval(function(){
$("#layer-1").fadeIn(1000, function() {
$("#layer-2").fadeIn(1000, function() {
$("#layer-3").fadeIn(1000, function() {
$("#layer-4").fadeIn(1000, function() {
$("#layer-5").fadeIn(1000, function() {
$("#layer-6").fadeIn(1000, function() {
$("#layer-7").fadeIn(1000, function() {
$("#layer-8").fadeIn(1000, function() {
// pause for 2 seconds, then reset and hide all layers:
$("#home-map .layer").delay(2000).fadeOut();
});
});
});
});
});
});
});
});
}, 10000)
Edit: The reason I think this is different to other answers is because I was trying to set things up in an infinite loop, as well as chaining the animations. There are lots of approaches to solving callback hell in javascript and its a very common sticking point, so no doubt there will be similar questions.
Upvotes: 7
Views: 871
Reputation: 138267
Challenge accepted... using an recursive approach.
(function main(index){
if(index >= 9){
return $.when($("#home-map .layer").delay(2000).fadeOut()).then(function(){
main(1); //restart
});
}
$("#layer-"+index).fadeIn(1000, function(){
main(index+1);
});
})(1);
http://jsbin.com/rurokipipi/1/edit?output
Upvotes: 5
Reputation: 37815
By using async await + promises
if you call .promise()
after a animation you will get a promise back. thanks to it you can be able to wait for it to finish
async function animate() {
await $("#layer-1").fadeIn(1000).promise()
await $("#layer-2").fadeIn(1000).promise()
await $("#layer-3").fadeIn(1000).promise()
await $("#layer-4").fadeIn(1000).promise()
await $("#layer-5").fadeIn(1000).promise()
await $("#layer-6").fadeIn(1000).promise()
await $("#layer-7").fadeIn(1000).promise()
await $("#layer-8").fadeIn(1000).promise()
// pause for 2 seconds, then reset and hide all layers:
await $("#home-map .layer").delay(2000).fadeOut().promise();
}
const loop = () => animate().then(loop)
loop()
Looking at this makes a for loop fit in very well.
async function animate() {
for (let i = 1; i < 9; i++)
await $(`#layer-${i}`).fadeIn(1000).promise()
// pause for 2 seconds, then reset and hide all layers:
await $("#home-map .layer").delay(2000).fadeOut();
}
This is only possible in most recent browser
Guessing it can be made more dynamic if you did
for (let layer of $("#home-map .layer"))
await $(layer).fadeIn(1000).promise()
Here is a es5 alternativ to same problem but using reduce to chain promises
function animate() {
$(".layer").toArray().reduce(function(prev, elm){
return prev.then(function(){
return $(elm).fadeIn(1000).promise();
})
}, Promise.resolve()).then(function(){
// do the master
$(".masterLayer").delay(2000).fadeOut();
})
}
animate()
.layer {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="masterLayer">
<div class="layer">layer 1</div>
<div class="layer">layer 2</div>
<div class="layer">layer 3</div>
<div class="layer">layer 4</div>
<div class="layer">layer 5</div>
<div class="layer">layer 6</div>
</div>
Upvotes: 7
Reputation: 8206
use a class instead of ids, then loop through them and add delay based on their index
var layers = $(".layer").length;
function foreverLoop() {
$(".masterLayer").show();
$(".layer").hide();
$(".layer").each(function(index) {
$(this).delay(1000*index).fadeIn(1000);
});
$(".masterLayer").delay(1000*layers + 2000).fadeOut(1000);
setTimeout("foreverLoop()", 1000*layers + 3000 + 500);
}
foreverLoop();
.layer {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="masterLayer">
<div class="layer">layer 1</div>
<div class="layer">layer 2</div>
<div class="layer">layer 3</div>
<div class="layer">layer 4</div>
<div class="layer">layer 5</div>
<div class="layer">layer 6</div>
</div>
Upvotes: 6
Reputation: 3594
var MAX_LAYER_NUMBER = 8;
var LAYER_FADE_IN_DURATION = 1000;
var LAYER_FADE_OUT_DURATION = 1000;
var LAYER_FADE_IN_OUT_DELAY = 2000;
function fadeInLayer(layerNumber, done) {
if (layerNumber <= MAX_LAYER_NUMBER) {
$('#layer-' + layerNumber).stop(true, true).fadeIn(LAYER_FADE_IN_DURATION, function() {
done(layerNumber);
fadeInLayer(layerNumber + 1, done);
});
}
}
function fadeInOutLayers() {
fadeInLayer(1, function(layerNumber) {
if (layerNumber === MAX_LAYER_NUMBER) {
$(".layer").delay(LAYER_FADE_IN_OUT_DELAY).fadeOut(LAYER_FADE_OUT_DURATION);
}
});
}
$(function() {
fadeInOutLayers();
setInterval(fadeInOutLayers, LAYER_FADE_IN_DURATION * MAX_LAYER_NUMBER + LAYER_FADE_IN_OUT_DELAY + LAYER_FADE_OUT_DURATION)
});
.layer {
display: none;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<div id="layer-1" class="layer">layer-1</div>
<div id="layer-2" class="layer">layer-2</div>
<div id="layer-3" class="layer">layer-3</div>
<div id="layer-4" class="layer">layer-4</div>
<div id="layer-5" class="layer">layer-5</div>
<div id="layer-6" class="layer">layer-6</div>
<div id="layer-7" class="layer">layer-7</div>
<div id="layer-8" class="layer">layer-8</div>
Upvotes: 3
Reputation: 179086
What you have is a recurring series of asynchronous function calls. You could use promises to flatten your arrow code, but we're not talking about code that we expect to throw errors, so there's no need of asynchronous try..catch
, which is what promises are.
Asynchronous functions that happen in sequence are queues. jQuery has a nice queue
method, which is what I recommend here.
Now, because you're executing this queue on a variety of different elements, you'll need to pick a common element to store the core queue on. In this case, I'll use body
but you could use pretty much any element (I recommend the closest common parent to all of the elements that you're animating because it will allow you to use the structure on the page in multiple places without the queues interfering with one-another, but that's more of an advanced step).
The fx
queue is the default queue, which is where animations actually occur. We're going to want to manage this queue separately from the fx
queue so that other animations can happen alongside this queue.
function myAnimation() {
$('body')
// queue up the next step of the animation
.queue('my-animation', (next) => {
// `next` is a function that tells the queue to continue
// on to the next step. We pass next to the complete
// callback of the animation so that they can continue
// fluidly.
$('#player-1').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-2').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-3').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-3').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-4').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-5').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-6').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-7').fadeIn(1000, next);
})
.queue('my-animation', (next) => {
$('#player-8').fadeIn(1000, next);
})
// here we want to wait for a bit before continuing with
// the rest of the queue
.delay(2000, 'my-animation')
.queue('my-animation', (next) => {
$('#home-map .layer').fadeOut(next);
})
// here we repeat the animation so that a `setInterval` call
// is unnecessary, and so that we don't have to care how long
// the animation takes in total
.queue('my-animation', (next) => {
// queue up the next iteration of the animation
myAnimation();
// continue with the queued animation
next();
});
}
myAnimation(); // queue up the animation
$('body').dequeue('my-animation'); // start the animation
Now, this code is awfully repetitive. I'll leave it as an exercise to the reader to change it to a simple for
loop or whatever you'd like it to be. It's just an example of the overall simplification.
The code in this example is longer than the original, but don't let that fool you. Because it's a queue, it's easier to reason about and edit later. Steps can be inserted and removed without needing to change wait times or attempt to rebalance nested parenthesis/curly braces.
Upvotes: 1