jQuery 动画

jerryhome

贡献于2011-07-06

字数:0 关键词: jQuery插件 jQuery

jQuery 动画 Zengtan Email: zengtan1021@gmail.com QQ 520521086 js实现动画的原理跟动画片的制作一样.动画片是把一些差距不大的原 画以一定帧数播放.js动画是靠连续改变元素的某个 css 属性值,比如 left, top.达到视觉的动画效果. 这几年出现了不少优秀的 js游戏, 比如前段时间的《js版植物大战僵 尸》.其实 js 游戏主要就是这 4 个部分组成. 绘图, 移动, 碰撞检测, 逻 辑设定.如果用 jquery 来做的话, 前三项都会变得相当容易. 去年我也用 jquery 写了 2 个小游戏(jquery 坦克大战和 jquery 泡泡堂). 也写了自己的动画类.不过当时限于水平,没有深入分析 jQuery.fx 类的 实现. 这几天读过源码之后, 感受很多. jquery 的fx类虽然只有 600 多行代码.也没有牵涉到太高深的js知识.里面的逻辑却比较复杂,很多 处理也很精妙.当真正披荆斩棘弄懂这一部分之后,相信对 javascript 的 理解都会新上一层,当然也会更加喜欢 jquery. 闲话少说, 在看源码之前, 先大概了解一下 fx类的实现思想. 首先fxfxfxfx类非常依赖 jquery jquery jquery jquery 的队列机制,,,,没有这个东西的话,,,,一切都无从谈 起....有关 jquery jquery jquery jquery 队列机制,,,, 见http://www.javaeye.com/topic/783260 . 回忆一下这句代码 $(‘div’).show(1000).hide(1000); 让这个 div 在1000ms 内渐渐显示,然后再渐渐隐藏. 这2个动画是按次 序执行的.在 javascript 的单线程异步模式下,管理异步的函数是很难的. 在时间戳上, 既无法知道它准确的开始时间,又不能得到它准确的结束 时间. 由于可能发生线程阻塞, 这些时间并不精确. 而让它们有序的执 行, 最好的办法就是把元素上所有的动画都放入队列. 当第一个动画结 束后, 在回调函数里通知第二个动画执行. 好比有个公司招聘, 只有一个面试官,而有很多应聘者在排队等候, 一 个人面试完之后,出门的时候顺便告诉第二个人进去面试.以此反复. 而 作为面试官, 只需要通知第一个面试者. 对于jquery, 当你使用 animate 函数执行动画的时候,这个动画并没有 马上被执行, 它会先存入元素的队列缓存里. 然后看是不是已经有正在 执行的动画. 如果没有, 就取出队列里的第一个动画并且执行(此时队 伍里可能还有别人, 只是被 stop 函数暂停了), 如果有,那么要等到队列 前面的所有动画执行完之后才会被通知执行. 就好像, 现在来了一位应聘者, 他先看看是不是已经有人在里面面试. 如果没有, 那么他可以直接进去面试. 如果有, 他必须得加入到队伍的 最后一个. 等前面的人全部面试完了才轮到他. animate 函数的主要功能并不是执行动画. 它只作为 api 的入口,修正参 数.然后把参数扔给 fx类去执行动画. jquery 的动画模块也并没有细化 得太离谱, 有几个方法是比较重要的. jQuery.fn.animate 修正参数 jQuery.speed 静态方法, 帮助animate 修正参数, 并且重写回调函 数.重写回调函数大概就是 callback = function(){ callback(); $(this.dequeue()); } jQuery.fx 构造函数, 跟动画有关的具体操作都在这个构造函数的原 型方法里. jQuery.fx.tick 静态方法, 作为定时器的方法, 监控所有动画的执行 情况. 我们最好先抛开复杂的参数修正等枝枝叶叶.直接从主干部分下手.先分 析一下这个要使得这个动画类基本够用, 需要一些什么条件. 1 至少需要一个定时器来执行动画, 而且最好只有一个,即使有多个动 画需要同时执行. 毕竟 setTimeout 和setInterval 的开销是巨大的. 2 需要一个队列机制来管理动画顺序, jquery 已经提供了 queue 和 dequeue. 3 需要一种算法来计算属性的当前值.比如当前位置,当前大小. 4 需要一个具体执行动画的函数. 对于1, 我们制造一个 tick 函数来设置定时器, 并且在定时器中观察动 画的执行情况. 对于 2 直接用 jquery 的队列机制就可以了 对于 3 jquery 提供了默认的 swing 和 liner. 我们还可以用一些别的算 法, 比如 tween. 对于4 这个函数得自己构建.怎么构建随意.不过它最好还带上停止动 画等功能. 好吧, 现在正式开始.我们不在一开始就钻进 jquery 的源码里去.先模仿 jquery 的思想. 自己实现一个动画 fx类. 通过这个简化了的 fx类, 再 来反过头来了解 jquery. 实现原理是这样, 修正参数后为每个属性的动画都生成一个动画对象 fx. 比如一个元素要改变 left 和top, 那么为 left 和top 分别创建一个动画 fx 对象, 然后把这些 fx 对象都 push 到全局 timers 数组.在定时器里循环这 些fx 对象, 每隔一段时间把他们对应的元素属性重新绘制一帧.直到达到 指定的动画持续时间.这时触发这次 animate 的 callback 函数.并且从全局 timer 数组把元素的这些 fx 对象都清除出去.直到timers 里没有 fx 对象, 表示页面的动画全部结束,此时清空定时器.当然这里面少不了队列控制. 先定义一个数组和一个定时器. 数组用来装载页面上所有动画. 当数组 里没有动画时, 表示所有动画执行完毕, 这时清掉定时器. var timers = []; var timerId; 然后是 animate 函数. 我们叫它 myAnimate. 它传入 4个参数. 分别是 {"left":500}这样的 property-value 对象,动画持续时间, 动画算法, 回调函数. myAnimate 里调用一个 getOpt 函数. getOpt 的作用是把参数组装成对象 返回. 并且在回调函数里加上通知下一个动画执行的动作. 即.dequeue() $.fn.extend({ myAnimate: function(property, duration, easing, callback){ var operate = jQuery.getOpt(duration, easing, callback); //得到一个包含了 duration, easing, callback 等参数的对象. 并且 callback 已经被修正. $(this).queue(function(){ //把具体执行动画的函数放入队列, 注意这里如果队列中 //没有动画函数, 就直接执行这个匿名 function 了. var elem = this; $.each(property, function(name, value){ //遍历每个属性, 为每个属性的动画都生成一个 fx对象. var fx = new FX(elem, operate, name); var start = parseInt($(elem).css(name)); //计算属性开始的值 var end = value; //属性结束的值 fx.custom(start, end); //转交给 FX的prototype 方法 cunstom 执行动画 }) }) return this; //返回 this, 以便链式操作. } }) FX构造函数 function FX(elem, options, name){ this.elem = elem; this.options = options; this.name = name; } FX构造函数里只初始化 3个实例属性.比如 this.elem = elem. this.options={"duration": 500, "easing":"swing", callback:fn} this.name = "left" 其他属性留在后面动态生成. custom 方法 custom 方法的任务主要是把当前 fx对象放入全局的 timer,并且启动定 时器来观察动画执行情况. FX.prototype.custom = function(from, to){ this.startTime = jQuery.now(); //开始的时间, 和当前时间一起就可以计算已消耗时间. //再用已消耗时间和持续时间相比就知道动画是否结束. this.start = from; //属性初始的值 this.end = to; //属性动画后的值 timers.push(this); //把每个 fx对象都 push 进全局的动画堆栈. FX.tick(); //启动定时器. } FX.tick 用来监控动画执行情况 FX.tick = function(){ if (timerId) return; //只需要一个定时器,所以如果该定时器已经存在了,直接 return timerId = setInterval(function(){ for (var i = 0, c; c = timers[i++];){ //每隔13ms, 遍历timerId 里的每个动画对象 fx, 让它们执行下 //一步. c.step(); } if (!timers.length){ //如果timers 没有元素, 说明页面的所有动画都执行完毕, 清除定时 器. //这个全局定时器就像一个总考官, 它会每隔一段时间巡视每个考生, //督促他们赶紧答题. //如果考生全部考试完毕交卷了, 他会进入休息状态. //这时如果又进来了考生, 他又进入工作状态. FX.stop(); } }, 13); } FX.stop 清空定时器 FX.stop = function(){ clearInterval(timerId); timerId = null } FX.prototype.step 执行每一步动画 FX.prototype.step = function(){ var t = jQuery.now(); //当前时间 var nowPos; //当前属性值 if (t > this.startTime + this.options.duration){ //如果现在时间超过了开始时间 + 持续时间, 说明动画应该结束了 nowPos = this.end; //动画的确切执行时间总是 13ms 的倍数, 很难刚好等于要求的动画持续 //时间,所以一般可能会有小小的误差, 修正下动画最后的属性值. this.options.callback.call(this.elem); //执行回调函数 this.stop(); //把已经完成动画的任务 fx对象从全局 timer 中删除. }else{ var n = t - this.startTime; //动画已消耗的时间 var state = n / this.options.duration; var pos = jQuery.easing[this.options.easing](state, n, 0, 1, this.options.duration); nowPos = this.start + ((this.end - this.start) * pos); } //根据时间比和 easing 算法, 算出属性的当前值 this.update(nowPos, this.name); //给属性设置值 } FX.prototype.stop 从全局 timer 中删除一个已经完成动画任务的 fx对象. FX.prototype.stop = function(){ for ( var i = timers.length - 1; i >= 0; i--){ if (timers[i] === this){ timers.splice(i, 1); } } } FX.prototype.update 给属性设置值 FX.prototype.update = function(value, name){ this.elem.style[name] = value; } 注意FX.stop 和FX.prototype.stop 是不同的. 前者是取消定时器, 这 样页面的所有动画都会结束. 后者是停止某个 fx对象的动画.比如left, opacity. 下面是可供测试的全部代码
我是一个 div
我是另一个 div
--------------------分割线--------------------------------- 上面的代码跟 jquery 有少许不同, 因为我写的时候有的地方忘记 jquery 是怎么搞的了.不过思路还是一样的. 尽管这样, jquery 的做法要麻烦很多. 毕竟作为一个库, 要考虑的东西 是非常非常多的. 一行一行来看代码. 首先定义一个常量 fxAttrs = [ // height animations ["height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations ["width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations ["opacity" ] ] 这个东西是为了得到当用动画形式执行 show,hide,slideDown, slideUp 时,所需要改变的属性值.比如show,需要改变的并不仅仅是 width 和 height.还包括 marginLeft,paddingLeft 等属性. jQuery.prototype.show show 和hide 这两个常用的方法实现都比较简单. show 和hide 可以直接 隐藏/显示元素,也可以以动画渐变的方式来达到效果.直接隐藏和显示 就是设置 display 属性,动画效果则要用 animate 函数改变 width,height,marginLeft 等.不过设置 display 的时候还要考虑 css 属 性对元素可见性的影响,以及尽量避免 reflow 回流.留在 css 部分讨论. jQuery.prototype.toggle 切换元素的可见状态, 就是 show 和hide 这两个操作集中到一起. toggle 有好几种调用方式, 1111 不传递任何参数,,,, 这时仅仅只切换元素的可见状态 2222 只有一个 booleabooleabooleabooleannnn类型的参数.... 这个参数实际是一个返回值为 booleabooleabooleabooleannnn 的表达式.... 当这个表达式结果为 true true true true 的时候显示元素,,,, 反之隐藏.... 即 toggle(switch)toggle(switch)toggle(switch)toggle(switch)形式.... 比如 varvarvarvar flipflipflipflip ==== 0;0;0;0; $("button").click(function$("button").click(function$("button").click(function$("button").click(function ()()()(){{{{ $("p").$("p").$("p").$("p").toggletoggletoggletoggle(((( flip++flip++flip++flip++ %%%% 3333 ======== 0000 );););); });});});}); 3 传入一些函数, 当点击元素的时候, 按顺序执行这些函数的某一个. 4 可以传入 3个参数, 分别为 speed, easing, callback. 即以动画的 形式切换可见性. 这几个方法的源码都跟动画的核心机制关系不大, 反而是和 css, evnet 模块联系比较密切. 就不放在这里讨论了. 现在看看关键的 animate 函数.前面讲过, animate 的作用主要是修正参 数, 把参数传递给 fx类去实现动画. animate: function( prop, speed, easing, callback ){ //prop 是{"left":"100px", "paddingLeft": "show"}这种形式, //可以由用户自己传进来, 在show, hide 等方法里, 是由 genfx //函数转化而来. var optall = jQuery.speed(speed, easing, callback); //optall 是一个包含了修正后的动画执行时间, 动画算法, 回调 //函数的对象. //speed 方法把 animate 函数的参数包装成一个对象方便以后调用, //并且修正 callback 回调函数, 加上 dequeue 的功能. if ( jQuery.isEmptyObject( prop )){ //prop 是个空对象.不需要执行动画,直接调用回调函数. return this.each( optall.complete ); } return this[ optall.queue === false ?"each" : "queue" ](function() { //如果参数里指定了 queue 为false, 单独执行这次动画, //而不是默认的加入队列. var opt = jQuery.extend({}, optall), p, //复制一下 optall 对象. isElement = this.nodeType === 1, //是否是有效 dom 节点. hidden = isElement && jQuery(this).is(":hidden"), //元素的可见性 self = this; //保存 this 的引用, 在下面的闭包中 this 指向会被改变 for ( p in prop ){ //遍历"left", "top"等需要执行动画的属性. var name = jQuery.camelCase( p ); //把margin-left 之类的属性转换成 marginLeft if ( p !== name ){ prop[ name ] = prop[ p ]; //把值复制给 camelCase 转化后的属性 delete prop[ p ]; //删除已经无用的属性 p = name; } if ( prop[p] === "hide" && hidden || prop[p] === "show" &&!hidden ){ //元素在 hidden 状态下再隐藏或者 show 状态下再显示 return opt.complete.call(this); //不需要任何操作, 直接调用回调函数. } if ( isElement &&( p === "height" || p === "width" )) { //如果是改变元素的 height 或者 width opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ){ if (!jQuery.support.inlineBlockNeedsLayout ) { //对于不支持 inline-block inline-block inline-block inline-block 的浏览器,,,,可以加上 zoom:1zoom:1zoom:1zoom:1 来hackhackhackhack ////////http://www.planabc.net/2007/03/11/display_inline-block/http://www.planabc.net/2007/03/11/display_inline-block/http://www.planabc.net/2007/03/11/display_inline-block/http://www.planabc.net/2007/03/11/display_inline-block/ this.style.display = "inline-block"; } else { var display = defaultDisplay(this.nodeName); if ( display === "inline" ){ this.style.display = "inline-block"; } else { this.style.display = "inline"; this.style.zoom = 1; } } } } if ( jQuery.isArray( prop[p] )){ (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; // 可以给某个属性单独定制动画算法. //例如$("#div1").animate({"left":["+=100px", "swing"], 1000}, prop[p] = prop[p][0]; //修正 prop[p] } } if ( opt.overflow != null ){ this.style.overflow = "hidden"; //动画改变元素大小时, overflow 设置为 hidden, 避 //免滚动条也跟着不停改变 } opt.curAnim = jQuery.extend({}, prop); //又复制一次 prop, opt.curAnim 用来记录某个元素中已经完成动画的 //属性, 比如 left 和 marginLeft 的动画并不是同一时间完成的,而只有 //全部属性的动画都完成之后, 才能触发这个元素的这次动画的 //callback 函数. jQuery.each( prop, function( name, val ){ //进入重点, 遍历属性. var e = new jQuery.fx( self, opt, name ); //fx 是个辅助工具类. 为每个属性的动画操作都生成一 //个fx对象. //得到一个具有 elem(被操作对象), options(包括speed, //callback 等属性), prop(elem 待变化的属性, 比如 left, top), //orig 为{}, 用来保存属性的原始值 if ( rfxtypes.test(val) ){ e[ val === "toggle" ? hidden ?"show" :"hide" : val ]( prop ); //动画方式进行 show. hide 和toggle. //比如$("#div1").animate({"width":"hide"}, 1000) //这里进入是 fx.prototype.show,fx.prototype.hide 方法 //并不是 jQuery.fn.show, jQueyr.fn.hide } else { var parts = rfxnum.exec(val), start = e.cur(true) || 0; //修正开始的位置, 如果是一个太大的负数,把它 //修正为 0 if ( parts ){ var end = parseFloat( parts[2] ), //结束的位置 unit = parts[3] || "px"; //修正单位 // We need to compute starting value if ( unit !== "px" ){ self.style[ name ] = (end || 1) + unit; start = ((end || 1) / e.cur(true)) * start; self.style[ name ] = start + unit; } //修正开始的位置,unit 可能为%. if ( parts[1] ){ end = ((parts[1] === "-=" ?-1 : 1) * end) + start; //做相对变化时, 计算结束的位置, 比如 //{"left":"+=100px"}, 表示元素右移 100 个像素 } e.custom( start, end, unit ); //调用 fx类的 prototype 方法 custom, 真正开始动画 } else { e.custom( start, val, ""); } } }); // For JS strict compliance return true; }); } 看看animate 里的speed 函数. 前面说过 speed 函数是把 animate 的参 数都包装为一个对象. 然后把 dequeue 加入回调函数.如果 animate 的第 二个参数是 object 类型,直接复制一次就可以了, 如果不是, 要自己手 动包装. speed: function( speed, easing, fn ){ var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) :{ complete: fn || !fn && easing || jQuery.isFunction( speed )&& speed, duration: speed, easing: fn && easing || easing &&!jQuery.isFunction(easing) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; //jQuery.fx.off 为true 时,禁止执行动画,此时任何 animate //的speed 都为 0 //如果 speed 为"fast","slow"等, 从jQuery.fx.speeds 里取 //值, 分别为 600,200. opt.old = opt.complete; opt.complete = function() { //重写回调函数, 把dequeue 操作加入回调函数. //让动画按顺序执行 if ( opt.queue !== false ){ //前面的 animate 方法里已经说过, queue 参数为 false 时, // 是直接执行此次动画, 不必加入队列. jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old )){ opt.old.call( this ); } }; return opt; //返回包装后的对象 } jQuery.fx 再看看 fx 类的实现. 它的构造方法接受 3 个从 animate 函数里传来的参 数. 每个实例在构造函数里初始化的时候都会被加上 3个属性. 分别是 options({“duration”:1000,“easing”:“swing”,“callback”:fn}) elem (元素本身) prop ({"left": 500, "width": 500}) 这几种形式 fx: function( elem, options, prop ){ this.options = options; this.elem = elem; this.prop = prop; if (!options.orig ){ options.orig = {}; } } }) 另外, 还会给每个对象实例绑定 orig 属性, 默认是一个空对象{}.用来 储存元素的属性原始值, 有了这个值, 可以让动画进行回退. 现在终于到了 custom 函数. 前面的这么多代码都只是铺垫, 这个函数里 才开始真正的进入执行动画. 它主要作用是创建定时器. // Start an animation from one number to another custom: function( from, to, unit ){ this.startTime = jQuery.now(); this.start = from; this.end = to; this.unit = unit || this.unit || "px"; this.now = this.start; this.pos = this.state = 0; var self = this, fx = jQuery.fx; function t( gotoEnd ){ return self.step(gotoEnd); } //一个包裹动画具体执行函数的闭包, 之所以要用闭包包裹起来, //是因为执行动画的动作并不一定是发生在当前. t.elem = this.elem; //保存一个元素的引用. 删除和判断 is:animated 的时候用到, //把timer 和元素联系起来 if ( t() && jQuery.timers.push(t) &&!timerId ){---(1) timerId = setInterval(fx.tick, fx.interval); } } 1处这个 if语句里包含了很多内容 首先 t()进入.step 函数, step 函数可以看成动画的某一帧. 而custom 函数至多只负责执行动画的第一帧(其它帧都是在全局定时器 里执行的),然后判断 t()的返回值, 即self.step(gotoEnd)的返回值, 如果为 false, 表示这个 fx 动画已经执行完.所以当它为 true,也就是还 需要继续执行动画的情况下,要把这个包含 self.step(gotoEnd)语句的 闭包, 存入全局的 timers 数组. 以便在定时器里循环执行.我在前面的 模拟fx类的实现中, 选择的存入 fx对象,把判断 fx对象执行完毕的操 作放在 fx.step 里面. 效果是一样.不过觉得存 fx对象的话, 没有这么 难理解.而timerId 就是那个全局计时器,用来不停的执行 timer 数组里 残余的动画.只有所有的动画都执行完毕后, 才会关闭 timerId 定时器. 当然如果 t()返回false 和timerId 定时器已经存在的情况下,都不需要 再启动定时器. 定时器里的调用的函数是 fx.tick. 看看 fx.tick 的实现. tick: function() { var timers = jQuery.timers; //timer 里面包括所有当前所有在执行动画的函数. for ( var i = 0; i < timers.length; i++ ){ if (!timers[i]() ){ //timers[i]()就是上面的 t(),也就是 fx.step(gotoEnd); //动画如果完成了,fx.step(gotoEnd)是返回 false 的. 所以这里 //是当某个动画完成之后, 就在全局的 timer 中删除它 timers.splice(i--, 1); } } if (!timers.length ){ //当全部动画完成之后, 清空定时器 jQuery.fx.stop(); } } jQuery.fx.stop 方法很简单 stop: function() { clearInterval( timerId ); timerId = null; } 再看下 fx的一些原型方法,最重要的就是 fx.prototype.step. 认真分析下这个函数的实现. step 函数用来执行动画的某一帧, 它有一个可选的参数 gotoEnd. 当gotoEnd 为true 时,直接让元素转到动画完成的状态.主要用在 $('div').stop 的时候. step 函数每次被调用的时候都会根据 gotoEnd 参数和当前时间跟开始时 间加上持续的时间的比值,来判断动画是否结束.如果结束了就返回 false. 在定时器下一次的循环里继续执行. 否则, 会算出元素在这一帧上面的状态, 调用fx.prototype.update 来 在页面上绘制这一帧.并且返回 true,在定时器下一次开始循环 timer 数 组的时候, 把包含这个 step 函数的闭包给删除掉. // Each step of an animation step: function( gotoEnd ){ var t = jQuery.now(), done = true; if ( gotoEnd || t >= this.options.duration + this.startTime ){ //如果 gotoEnd 为true 或者已到了动画完成的时间. this.now = this.end; this.pos = this.state = 1; //动画的确切执行时间总是一个指定的 number 的倍数, 很难刚 //好等于要求的动画持续时间,所以一般可能会有小小的误差, 修正下 //动画最后的属性值. this.update(); //进入 uptate,重新绘制元素的最后状态. this.options.curAnim[ this.prop ] = true; //某个属性的动画已经完成. for ( var i in this.options.curAnim ){ if ( this.options.curAnim[i] !== true ){ //如果有一个属性的动画没有完成,都不认为该次 animate 操 //作完成, 其实这里完全可以事先用一个 number 计算出有多 //少个需要执行动画的属性.每次 step 完成就减 1, 直到等于 0. //效率明显会高一点. done = false; } } if ( done ){ // 恢复元素的 overflow if ( this.options.overflow != null &&!jQuery.support.shrinkWrapBlocks ){ var elem = this.elem, options = this.options; jQuery.each( ["","X","Y"], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; }); } // Hide the element if the "hide" operation was done if ( this.options.hide ){ jQuery(this.elem).hide(); //如果要求 hide, 动画完成后,通过设置 display 真正隐 //藏元素.动画的最后只是设置 width,height 等为 0. } if ( this.options.hide || this.options.show ){ for ( var p in this.options.curAnim ){ jQuery.style( this.elem, p, this.options.orig[p] ); } } //hide,show 操作完成后,修正属性值. 通过 hide/show 动 //画开始前记录的原始属性值 //执行回调函数 this.options.complete.call( this.elem ); } return false; //动画完成, 返回 false } else { //动画还没执行完. var n = t - this.startTime; //动画执行完还需要的时间 this.state = n / this.options.duration; //还需要时间的比例 // Perform the easing function, defaults to swing var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; //某个属性如果指定了额外的动画算法. var defaultEasing = this.options.easing || (jQuery.easing.swing ?"swing" :"linear"); //所有属性公用的动画算法, 如果没有指定. 默认为"swing" 或者"linear" this.pos=jQuery.easing[specialEasing|| defaultEasing](this.state, n, 0, 1, this.options.duration); //通过动画算法, 计算属性现在的位置. this.now = this.start + ((this.end - this.start) * this.pos); this.update(); //重新绘制元素在这一帧的状态 } return true; //此fx动画还未结束, 返回 true. } }; jQuery.fx.update update: function() { if ( this.options.step ){------------------(1) this.options.step.call( this.elem, this.now, this ); } (jQuery.fx.step[this.prop]||jQuery.fx.step._default)( this ) ------------------(2) } 从update 方法的(1)处可以看到, 当animate 的第二个 object 类型参 数有step 属性, 并且值是一个函数的话,在每一帧动画结束后, 都会执 行这个 callback 函数. (2)处绘制元素是调用的 jQuery.fx.step, 不是刚才一直在讨论的 fx.prototype.step. 看看 jQuery.fx.step 的代码. step: { opacity: function( fx ){ jQuery.style( fx.elem, "opacity", fx.now ); // opacity opacity opacity opacity 的操作不一样.IE .IE .IE .IE 要用 filter:alphafilter:alphafilter:alphafilter:alpha ....拿出来单独处理.... }, _default: function( fx ){ if ( fx.elem.style && fx.elem.style[ fx.prop ]!= null ) { fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } -----------------------分割线------------------------------- 到这里, fx类的中心实现就基本差不多了. 最后看看另外一些用到过的 方法. jQuery.fn.stop 这个方法让元素停止动画, 可以传入 2个boolean 类型的参数, clearQueue 和 gotoEnd. 作用分别是在 stop 的同时, 清空元素队列里的 动画, 和让元素立即转换到动画结束后应该处在的状态(默认是动画执 行一半的时候 stop 的话, 元素会停留在那个状态). stop: function( clearQueue, gotoEnd ){ var timers = jQuery.timers; if ( clearQueue ){ this.queue([]); //清空元素的队列 } this.each(function() { for ( var i = timers.length - 1; i >= 0; i-- ){ //循环全局 timers if ( timers[i].elem === this ){ //找到 timers 中的跟这个元素对应的 fx闭包, //可能有 N个. if (gotoEnd) { timers[i](true); //调用一次 fx.step(true),转到动画执完成的状态 } timers.splice(i, 1); //timers 数组里删掉这个 fx闭包 } } }); if (!gotoEnd ){ this.dequeue(); } return this; } }) fx.prototype.show 和fx.prototype.hide show: function() { //Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.show = true; this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); //把width 和height 设置为一个很小的值, 防止屏幕闪烁. // Start by showing the element jQuery( this.elem ).show(); }, // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation this.custom(this.cur(), 0); } 这2个方法是用于 $("#div1").animate({"width":"hide"},{duration:1000}).animate({ "width":"show"}, 1000)这种情况. 当元素 hide 之前, 需要记录一下原来的 width, 以便在接下来的 show 操作的时候, 回到原来的 width. 然后是一些常用快捷方法. jQuery.each({ slideDown: genFx("show", 1), slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" } }, function( name, props ){ jQuery.fn[ name ] = function( speed, easing, callback ){ return this.animate( props, speed, easing, callback ); }; }) 可以看到, 都是调用的 animate 函数. genFx fxAttrs = [ // height animations ["height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations ["width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations ["opacity" ] ] function genFx( type, num ){ var obj = {}; jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { obj[ this ] = type; }); return obj; } 这个方法就是取得进行一些特殊的动画操作时, 元素需要改变的属性. 返回的是一个{key: value}类型的对象.

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档