11.2 观测及中断动画

刚刚完成的这个基本的动画暴露出一个问题。如果mouseentermouseleave事件发生后,有足够的时间让动画完成,动画就可以达到预期效果。可是,一旦触发这两个事件的速度太快,反复次数太多,图像则会在最后一次事件触发后反复多次增大和缩小。之所以会这样,原因正如第4章所介绍的,一个给定元素的动画会逐一被添加到一个队列中,然后再依次调用。第一个动画会立即被调用,在指定时间内完成,然后从队列中移除。此时,第二个动画又排在了第一位,于是接着被调用,完成,移除,以此类推,直至队列为空。

很多情况下,jQuery中这个叫做fx的动画队列都不会给我们带来问题。不过,在遇到像我们前面例子中这种悬停动画时,就要跟这个队列斗斗智了。

11.2.1 确定动画状态

若要避免产生不合需要的动画队列,一种方式是使用jQuery自定义的:animated选择符。在mouseenter/mouseleave事件处理程序中,可以使用这个选择符来检测图像,看它当前是否正处于动画的过程中,如代码清单11-2所示。

代码清单11-2

  1. $(document).ready(function() {
  2. $('div.member').on('mouseenter mouseleave', function(event) {
  3. var $image = $(this).find('img');
  4. if (!$image.is(':animated') || event.type == 'mouseleave') {
  5. var size = event.type == 'mouseenter' ? 85 : 75;
  6. var padding = event.type == 'mouseenter' ? 0 : 5;
  7. $image.animate({
  8. width: size,
  9. height: size,
  10. paddingTop: padding,
  11. paddingLeft: padding
  12. });
  13. }
  14. });
  15. });

当用户的鼠标进入成员(member<div>时,图像应该只在它已经完成动画时再开始新动画。当鼠标离开时,不管是什么情况都应该立即开始动画,因为鼠标离开之后就应该立即恢复原来的大小和内边距。

这样,我们就成功地避免了代码清单11-1那失控的动画,但现在的动画仍然还需要改进。当鼠标快速进入和离开<div>时,图像仍然会完成整个mouseenter动画(增大),然后才开始mouseleave动画(缩小)。说实话,这并不是理想的效果,但测试:animated伪类又引入了一个更大的问题:如果鼠标进入<div>时,图像正在“缩小”,那么此后的图像也不会再增大了。只有当动画完成之后发生的mouseentermouseleave事件,才会引发另一次动画。这说明,尽管:animated选择符适用于在某些情况下检测动画状态,但在我们这里还不够。

11.2.2 中止运行的动画

好在,jQuery还有一个方法,可以帮我们解决代码清单11-2中存在的两个问题。这个方法就是.stop(),它能在动画运行过程中让动画立即停止。为了利用这个方法,我们要回到代码清单11-1的方案上来。只要在.find().animate()之间插入.stop()即可,参见代码清单11-3。

代码清单11-3

  1. $(document).ready(function() {
  2. $('div.member').on('mouseenter mouseleave', function(event) {
  3. var size = event.type == 'mouseenter' ? 85 : 75;
  4. var padding = event.type == 'mouseenter' ? 0 : 5;
  5. $(this).find('img').stop().animate({
  6. width: size,
  7. height: size,
  8. paddingTop: padding,
  9. paddingLeft: padding
  10. });
  11. });
  12. });

这里的关键是在处理新动画之前先停止当前动画。这样,即使鼠标反复进入和离开,之前我们遇到的问题也不会再出现了。因为当前动画总是会立即完成,因而fx队伍中的动画从来都不会超过1个。在鼠标最后不再活动的时候,最后一个动画就会完成。结果根据最后一次触发的事件,图像不是完全增大(mouseenter),就是收缩回其原来大小(mouseleave)。

中止动画的注意事项

由于.stop()方法默认情况下会在动画的当前位置中止动画,因而在使用简写动画方法的情况下,就有可能导致意外的结果。在动画之前,这些简写的动画方法会确定最终的值,然后动态变化到该值。比如说,如果使用 .stop().slideDown() 动画的中途将其中止,然后调用.slideUp()。那么下次再在同一个元素上调用.slideDown()时,就只会向下滑动到上一次停止时的高度。为了解决这个问题,.stop()方法可以接收两个布尔值参数(true/false),其中第二个参数叫goToEnd。如果把这个参数设置为true,那么当前动画不仅会停止,而且会立即跳到最终值。当然,这样做的结果就是看起来有点突兀。所以更好的办法是把最终值保存在一个变量中,使用.animate()显式变化到该值,而不要依赖jQuery确定的值。

 jQuery还有一个方法可以中断动画:.finish()。这个方法与.stop(true, true)效果类似,因为它会清除排队的动画并使当前动画跳到最终值。不过,与.stop(true, true)不同的是,它也会使所有排队的动画都跳到各自的最终值。