JavaScipt 动画引擎

Posted on 2017-01-10 18:32  黄银  阅读(174)  评论(0编辑  收藏  举报

队列操作

jquery中有一个Queue队列的接口,这个模块没有单独拿出来作为一个章节是因为这个是内部专门为动画服务的,Queue队列如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施

Queue队列

队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行出入操作(入队),队列的特点是先进先出,最先插入的元素最先被删除。

为什么要引入队列

var a = 1;

setTimeout(function(){

  a=2;

},0)

alert(a);

我们一直习惯于线性的编写代码逻辑,但是在JavaScript编程几乎总是伴随着异步操作:

setTImeout,css3Transition/Animation,ajax,dom的绘制,postmessage,web Database 等等,大量异步操作所带来的回调函数会把我们的算法分解,对于“异步+回调”的模式,怎么“拉平”异步操作使之跟同步一样,因为异步操作进行流程控制的时候无非避免的要嵌套大量的回调逻辑,所以就会出现 promises 约定了。

那么 jQuery 引入队列其实从一个角度上可以认为:允许一系列函数被异步地调用而不会阻塞程序

看一个代码段:

$("#Aaron").slideUp().fadeIn()

这是 jQuery 的一组动画链式序列,它的内部其实就是一组队列 Queue,所以队列和 Deferred 地位类似,是一个内部使用的基础设施。

  • 当 slideUp 运行时,fadeIn 被放到 fx 队列中
  • 当 slideUp 完成后,从队列中被取出运行

Queue 函数允许直接操作这个链式调用的行为,同时 Queue 可以指定队列名称获得其他能力而不局限于 fx 队列。

jQuery 提供了 2 组队列操作的 API:

jQuery.queue/dequeue
jQuery.fn.queue/dequeue

但是不同与普通队列定义的是:

  • jQuery.queue 和 jQuery.fn.queue 不仅执行出队操作返回队头元素,还会自动执行返回的队头元素
  • fn 是扩展在原型上的高级API是提供给实例使用的
  • .queue/.dequeue 其内部是调用的 .queue,.dequeue 静态的底层方法实现入列与出列

queue与dequeue

$.queue:显示或操作匹配的元素上已经执行的函数队列这个方法有两个作用,它既是setter,又是getter,第一个参数elem是DOM元素,第二个参数type是字符串,第三个参数data可以是function或数组。

var body  = $('body');

function cb1(){alert(1)}

function cb2(){alert(2)}

 

//set 第三个参数是函数

$.queue(body,'aa',cb1);

$.queue(body,'aa'cb2);

//get

$.queue(body,'aa');

这个方法有点类型get有点类似队列的push操作,jQuery的方法的接口重载是非常严重的,经常同一个接口既是set也是get,不管符不符合基本原则,但是它却很实用,无非就是把数据给缓存起来,为什么载体是一个jQuery对象,因为保存数据的手段是通过data数据缓存实现的

data 与 jQuery 对象之间是通过 uuid 建立了一个无耦合的映射关系,具体可以翻阅之前的关于“数据缓存”,源码有一个默认处理 type = (type || "fx") + "queue" 可见是专职供fx动画队列处理的。

这里有一个hooks?

仔细分析下这个内部 queueHooks
_queueHooks: function(elem, type) {
    var key = type + "queueHooks";
    return data_priv.get(elem, key) || data_priv.access(elem, key, {
        empty: jQuery.Callbacks("once memory").add(function() {
            data_priv.remove(elem, [type + "queue", key]);
        })
    });
}
我们说了dequeue不仅是取出来还需要执行,在执行的时候把next与hooks传递给外部的回调,这就是js的逻辑上的很绕的地方,在内部可以传递一个引用出去,又能提供外部调用或者执行。
fn.call(elem, next, hooks)
因为传递了next,所以我们的代码可以参考右边代码,next内部仍然调用$.dequeue,这样可以接着执行队列中的下一个 callback,$.dequeue 里的 hooks 是当队列里所有的 callback 都执行完后(此时 startLength 为0)进行最后的一个清理工作。
if ( !startLength && hooks ) {
    hooks.empty.fire();
}
钩子其实就是 jQuery.Callbacks 对象,可以实现一个收集器的功能,至于在什么情况下时候,之后动画中开始分析,所以队列的本质是利用 Array 的 push 和 shift 来完成先进先出(First In First Out),但是这个方法的缺点也很明显,无法单独做一个独立的模块处理,因为它必须要跟 jQuery 对象吻合,而且对传递的数据只能是函数。
 
动画思路
book.animate({
    left: '+=50',
}).animate({
    left: '+=100',
}).animate({
    left: '-=50',
});
动画是异步的,但是animate方法的链式代码是同步的,所以这里要涉及一个最重要的问题,动画队列要如何有序的调用。
传统的思路:
定时器setTimeout方法收集,用一个数组保存每一个animate方法,把animate方法排成队列,之后开始执行动画
这里存在的一个严重的问题,让定时器产生足够长的时间差,那么这个时间差需要多长,显而易见这样的思路不是非常的精确。

jQuery 为动画量身定制了队列机制,我们的思路可以是这样

  • 有一个队列,在执行第一个 animate 方法的时候加入队列就开始执行动画,因为动画自己在执行的时候就会产生异步的时间差
  • 我们在这个时间差的里面继续去加入之后的动画 animate 进去队列,然后在每一个动画结束之后去取出队列中的第一个 animate 方法开始执行,依次循环下去

动画的参数

jQuery的内部的方法都是针对 API 的处理范围设计的,我们看看 Animation 方法的支持情况:

.animate( properties [, duration ] [, easing ] [, complete ] )
.animate( properties, options )
  • 区别就与第二组数据的传递,options 是支持对象传参
  • properties 参数就是写一个 CSS 属性和值的对象,动画都是涉及变化的,那么什么值才能变化?
  • 理论上来说有数值的属性都是可以变化的,width, height 或者 left 可以执行动画,但是 background-color 不能,但是也不是绝对的,主要看数据的解析度,可以用插件支持
  • 除了样式属性, 一些非样式的属性,如 scrollTop 和 scrollLeft,以及自定义属性,也可应用于动画
  • 除了定义数值,每个属性能使用'show', 'hide', 和 'toggle'。这些快捷方式允许定制隐藏和显示动画用来控制元素的显示或隐藏。为了使用 jQuery 内置的切换状态跟踪,'toggle'关键字必须在动画开始前给定属性值