ZH CEXO's BLOG

header_bg
发表于 | 2 comments

jQuery中处理动画序列引起的问题

jQuery中的动画效果应该是相当出色的,让我们能够以非常简单的代码来实现很多复杂的效果,比如show()、hide()、slideDown()、slideUp()等。另外,如果给同一个元素指定不同的动画效果,那么这些动画效果是按照序列来执行的,比如如下这段代码:

$(document).ready(function(){
    $('#panel').css('opacity','0.5');
        $('#panel').click(function(){
            $(this).animate({
                left:'500px',
                height:'200px',
                opacity:'1'
            },3000).animate({
                top:'200px',
                width:'200px'
            },3000).css('border','5px solid blue');
        });
    });
});

代码先将#panel这个元素的透明度设置为0.5,之后元素会先向右移动500px、高度变为200px、透明度变为1,再然后元素会在第一段动画完成后,向下移动200px,并且高度变为200px。这两段动画其实就组成了一个动画序列,先执行第一段,然后执行第二段。

不过有时候,这种有动画序列却会引起一些问题,比如我想用jQuery中的slideDown()和slideUp()来制作有滑动效果的下拉菜单:鼠标悬浮于导航的时候菜单滑下,鼠标离开的时候菜单滑上,可是如果不作任何处理,把鼠标快速地在几个导航上快速滑入滑出,菜单节奏就会乱掉,上上下下像在跳舞一下:

DEMO 1

解决方法一:使用jQuery带的stop()方法

jQuery自带的stop()方法的描述是这样的

stop( [clearQueue] [, gotoEnd] )

两个参数均为可选,它们都是布尔值

clearQueue表示是否清空未执行完的动画序列

gotoEnd表示是否将正执行的动画跳转到末状态

如果两个参数均不设置,则它会立即停止当前元素的动画。如果动画序列中还有其他的动画,则会以当前状态开始执行接下来的动画

那么想想这个菜单的实际情况:鼠标移入的时候,实际它就会在动画序列里添加一个slideDown动画,然后在鼠标移出的时候再添加一个slideUp动画。因为动画是按顺序执行的,所以第二个动画非得等到第一个动画执行完毕了以后再执行。如果鼠标移入移出的速度过快,动画就会慢慢按序列里的顺序执行直到完毕,所以就会出现DEMO1中的效果。那么用stop()的方法来清空序列不就行了?是的,所以把stop()方法的第一个参数设置为true。

那第二个值呢?我们想想,我们的目标是把菜单完全展示于用户的面前,并且slideUp动画是需要基于之前slideDown的状态来完成的,你slideDown把菜单的整个高度都显示出来了,slideUp才好把菜单隐藏掉。另外还需要注意的一点是,slideUp和slideDown动画未执行完的时候,会把过程中的临时变量记录到DOM中,也就是说,如果你指定第二个参数为false,那么比如菜单高度有100px,但因为鼠标移出提前结束了,只展开了10px,那么菜单的高度就是10px并且被记录在DOM中了。再次使用slideDown的话,它就会以10px为基础展开,显然菜单并不会完整地展示于用户有面前。所以把第二个参数也设置为true,让它让元素动画直接跳转到末状态。

DEMO 2

大家可以把鼠标快速地在导航上滑入滑出试试看,虽然动画没有什么问题,但是能明显看到,如果在菜单展开的过程中移出了鼠标,菜单会立刻全部展开到末状态,而不是慢慢展开。关键代码如下

$(document).ready(function(){
    $('#menu > li').hover(function(){
        //这里的stop(true,true)指的是让动画队列清空,并且让已经在进行动画的元素到达动画的末状态,方便在第二次动画中使用
        $(this).find('ul').eq(0).stop(true,true).slideDown('slow');
    },function(){
        $(this).find('ul').eq(0).stop(true,true).slideUp('slow');
    });
});

解决方法二:使用setTimeout来判断鼠标悬停的时间

这种方法是mg12提出来的,感觉比第一种方法更好,菜单滑动的时候不突兀。关键代码如下:

$(document).ready(function(){
    //设定两个数组,分别对应在不同元素上鼠标滑入和滑出要触发的函数
    //鼠标滑入的数组
    var mouseover_tid = [];
    //鼠标滑出的数组
    var mouseout_tid = [];
    //使用each()方法,index为此方法自动传入的参数,记录匹配元素在元素集合中的所处位置
    $('#menu > li').each(function(index){
        $(this).hover(function(){
            //this为当前的li元素,这里赋给self变量,方便在setTimeout方法中引用
            var self = this;
            //先清除鼠标滑出时的slideUp方法
            clearTimeout(mouseout_tid[index]);
            //鼠标滑入时,把当前的鼠标滑放的方法记录到数组中,并判断鼠标停留的时间是否符合条件
            mouseover_tid[index] = setTimeout(function(){
                //如果这里不用self而用this,this指向的就不是当前的li这个DOM元素
                $(self).find('ul').eq(0).slideDown('slow');
            },150);
        },function(){
            //鼠标滑出时的说明与滑入时的说明相似,此处省略
            var self = this;
            clearTimeout(mouseover_tid[index]);
            mouseout_tid[index] = setTimeout(function(){
                $(self).find('ul').eq(0).slideUp('slow');
            },150);
        });
    });
});

DEMO 3

代码的关键位置我已经注释清楚了,你也可以去mg12的那篇文章里看看。与第一种方法的区别在于,由于没使用stop()方法,所以你不会看到很突兀的鼠标一移开,菜单马上全展开的时候的效果。

结语

这里只是用slideDown和slideUp举的例子,方法也可以适用于其他的情况,比如fadeIn和fadeOut、animate等。目前只介绍这两种方法,以后想到了再补充,另外,如果大家有更好的意见,欢迎反馈。

分类:JavaScript | 标签: ,

    已经有2次占座了,你也来凑个热闹吧

  1. 林木木2011年05月13日@10:52[回复]

    杀勒个发 —_—!
    一般人不怎么注意这点,下拉什么的加个stop还是蛮有必要的~

  2. ZH CEXO2011年05月14日@23:06[回复]

    @林木木:我以前基本是乱用stop(),不过现在理解了,会用得更合理一些吧
    我这里写这样的文章,就慢慢冷清了,哈哈

发表一下看法,占个座什么的,希望垃圾评论者离开

,欢迎您的再次光临!  [修改资料]

:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: