This is somewhat tricky. jQuery animation queues are geared around animating/stopping a single element. Making a well-behaved queue for multiple elements is not straightforward.
What do I mean by "well behaved"? The behaviour you want can be achieved in several ways see Kevin B's answer here. However, it's not so simple to stop an animation queue comprising multiple elements. That is an issue for rapid mouseenter-mouseleave actions. Try it in your own solution and you will find that the animation can become confused.
There's probably several ways to overcome this issue. Here's one involving promise chains plus a mechanism for killing them if a previous animation chain is still in progress.
var t = 700;
$(".respect-row").on('mouseenter', function () {
//In case an earlier animation is still in progress ...
//...(i) withdraw permission for any earlier animation to continue
var $this = $(this).data('continue', false);
//...(ii) stop everything.
$this.stop(true, true);
var $h2 = $this.find("h2").stop(true, true);
var $p = $this.find("p").stop(true, true);
// Animations, each defined as a named function that returns a promise
function anim1() {
if($this.data('continue')) {
return $h2.animate({
'marginTop': '0px',
'marginBottom': '20px'
}, t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
function anim2() {
if($this.data('continue')) {
return $this.animate({
'width': '320px'
}, t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
function anim3() {
if($this.data('continue')) {
return $p.fadeIn(t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
//Invoke forward animations with `continue` set to true
$this.data('continue', true);
anim1().then(anim2).then(anim3);
});
$(".respect-row").on('mouseleave', function revert() {
//In case an earlier animation is still in progress ...
//...(i) withdraw permission for any earlier animation to continue
var $this = $(this).data('continue', false);
//...(ii) and stop everything.
$this.stop(true, true);
var $h2 = $this.find("h2").stop(true, true);
var $p = $this.find("p").stop(true, true);
// Animations, each defined as a named function that returns a promise
function anim1() {
if($this.data('continue')) {
return $h2.animate({
'marginTop': '20px',
'marginBottom': '0px'
}, t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
function anim2() {
if($this.data('continue')) {
return $this.animate({
'width': '160px'
}, t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
function anim3() {
if($this.data('continue')) {
return $p.fadeOut(t).promise();//allow the rest of the animation chain to continue
} else {
return $.Deferred().reject().promise();//suppress the rest of the animation chain
}
}
//invoke reverse animations with `continue` set to true
$this.data('continue', true);
anim3().then(anim2).then(anim1);
});
DEMO - slowed down to make the sequence easier to observe.
I'm afraid the simplicity of your own solution has vanished and this won't be the most obvious solution unless you are used to working with promises, but you will find it to be reliable with regard to rapid mouse movements.
BTW: A previous attempt to use .queue()
and dequeue()
was more elegant in achieving the required animation sequences but suppressing it proved difficult. I gave up and retreated back to promises, which I understand.