Reputation: 742
Maybe this question is duplicate. I found a lot of similar questions, but no one can actually solve my problem. This is my task:
function animate(){
$ul.each(function(){
$(this).find('li').each(function(){
//animate block
$(this).animate({top:'-100%'},100,function(){
$(this).css({top:'100%'});
});
//endblock
});
});
}
As you may know, the 'animate block' functions will run at a same time. I want them to run in sequence. How can I achieve that?
I have read jQuery 'deferred', 'Q' relate article, but still confusing.
Sorry for my English.
---------addition------
If I want to run animate function several times, what should I do?
Upvotes: 0
Views: 151
Reputation: 1
Try utilizing .queue()
, $.map()
, .delay()
; substituting window.innerHeight
for 100
, px
unit for %
unit at .animate()
; substituting .animate
for .css
at complete
callback
var ul = $("ul");
ul.queue("_fx", $.map(ul.find("li"), function(el, i) {
return function(next) {
return $(el).animate({
top: "-" + window.innerHeight / 10 + "px"
}, 100, function() {
$(this).delay(100).animate({
top: window.innerHeight - $(this).height() * 1.5 + "px"
}, 100, next);
})
};
})).dequeue("_fx");
body {
height: 200px;
}
ul li {
top: 150px;
position: relative;
width: 50px;
height: 50px;
background: red;
padding 8px;
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<ul>
<li>a
</li>
<li>b
</li>
<li>c
</li>
<li>d
</li>
<li>e
</li>
<li>f
</li>
<li>g
</li>
</ul>
Upvotes: 0
Reputation: 19288
If you want to avoid jQuery's awkward .queue()
/.dequeue()
, then you can build a promise chain from a jQuery collection of your <li>
elements.
function animate($ul) {
var p = $.when(); //resolved starter promise
$("li", $ul).each(function(i, li) {
p = p.then(function() {
return $(li).animate({ top:'-100%' }, 100, function() {
return $(li).css({ top:'100%' });
}).promise();
});
});
return p;
}
As the chain settles, the animations will occur in sequence.
More elegantly, you can extend jQuery to have a .reduce()
method, adopted from Array.prototype
.
jQuery.fn.reduce = Array.prototype.reduce;
Then, build the required promise chain by invoking .reduce()
on the jQuery collection.
function animate($ul) {
return $("li", $ul).reduce(function(p, li) {//wow, jQuery has a .reduce() method!
return p.then(function() {
//Here, perform the required animation and return a promise of its completion.
return $(li).animate({top:'-100%'}, 100, function() {
$(li).css({top:'100%'});
}).promise();
});
}, $.when());//Resolved starter promise, ie p in the first iteration of .reduce() .
}
This pattern is well worth learning as it has many applications.
In both examples, the outermost return
allows you to chain .then()
wherever animate()
is called :
animate($("ul")).then(function() {
//do something when all the <li> animations have completed.
});
Upvotes: 0
Reputation: 56527
Something like this should work:
var last_deferred = $.Deferred();
last_deferred.resolve();
$ul.each(function(){
$(this).find('li').each(function(){
var deferred = $.Deferred(),
$that = $(this);
last_deferred.then(function() {
$that.animate({top:'-100%'},100,function(){
$(this).css({top:'100%'});
deferred.resolve();
});
});
last_deferred = deferred;
});
});
Explanation: here I use 2 important features of deferred objects .then
and .resolve
. When you call .resolve();
the deferred object will fire all handlers assigned to it via .then
. So what happens is that I first define "dummy" deferred which I immediately resolve. If deferred object is resolved then adding a handler via .then
will automatically make it run. So what happens is this
last_deferred
is defined and resolveddeferred
is created.then
handler to last_deferred
.then
handler is fired and after some things it does it resolves deferred
(thus triggers .then
on deferred
)last_deferred
is redefined to be deferred
So this is kind of an asynchronous loop (because .animate
is an asynchronous operation). In each step you say "when previous deferred
is resolved tell it to resolve the next one".
Upvotes: 0
Reputation: 11750
Check this solution using queue()
function.
This function waits until the animation completes and then executes it's contents.
var anim = function(i) {
$('.test').eq(i).animate({'top':'20px'}, 350).queue(function() {
i++;
anim(i);
$(this).dequeue();
});
}
anim(0);
.test {
width:50px;
height:50px;
float: left;
margin-right:10px;
background-color:green;
position:relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="test">test</div>
<div class="test">test</div>
<div class="test">test</div>
<div class="test">test</div>
<div class="test">test</div>
Upvotes: 1