jQuery的动画模块提供了包括隐藏显示动画、渐显渐隐动画、滑入划出动画,同时还支持构造复杂自定义动画,动画模块用到了之前讲解过的很多其它很多模块,例如队列、事件等等,

$.animate()的用法如下:animate(prop,speed,easing,callback),参数如下:

  • prop   ;对象,               ;包含将要动画的样式
  • speed   ;字符串或数字    ;动画运行持续时间,单位毫秒,可以设置为"slow"、"normal"、"fast"等预定义时间
  • easing  ;字符串            ;表示所使用的缓动函数
  • callback  ;回调函数           ;当动画完成时被调用

还是举个栗子:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
<style>
div{width: 200px;height: 50px;background: #cfc;}
</style>
</head>
<body>
<button>按钮</button>
<div></div>
<script>
$('button').click(function(){
$('div').animate({width:'400px',height:'100px',background:'#f00'},2000,'linear',function(){alert('动画完成成功')})
})
</script>
</body>
</html>

我们定义一个div和一个按钮,再在按钮上绑定一个事件,该事件回调用$.animate()来修改div的样式,动画完成后再执行一个回调函数,效果如下:

挺好用的啊,很方便的,可以用jQuery实现各种动画,尺寸、位置、颜色,还可以配合CSS3的transform属性实现各种酷炫效果。

源码分析


jQuery内在执行$.animate()时会遍历参数1也就是我们要修改的样式,然后分别创建一个对应的$.fx对象,实际上最终的动画是在$.fx这个对象上运行的,另外jQuery内部会维护一个setInterval(),默认会每隔13毫秒执行一次样式的修改,这样看起来就和动画一样了。

直接看代码吧,$.animate()实现如下:

jQuery.fn.extend({
animate: function( prop, speed, easing, callback ) { //动画入口函数,负责执行单个样式的动画,
var optall = jQuery.speed( speed, easing, callback ); //调用$.speed修正运行时间、缓动函数,重写完成回调函数 if ( jQuery.isEmptyObject( prop ) ) { //如果prop中没有需要执行动画的样式
return this.each( optall.complete, [ false ] ); //则立即调用完成回调函数
} // Do not change referenced properties as per-property easing will be lost
prop = jQuery.extend( {}, prop ); //复制一份动画样式的集合,以避免改变原始动画样式集合,因为每个动画样式的缓存函数最终会丢失。 function doAnimation() { //执行动画的函数,后面再讲解,我们先把流程理解了
/*略*/
} return optall.queue === false ? //queue表示是否将当前动画放入队列
this.each( doAnimation ) : //如果optall.queue为false,则调用.each()方法在每个匹配元素上执行动画函数doAnimation();
this.queue( optall.queue, doAnimation ); //否则调用方法.queue()将动画函数doAnimation()插入选项queue指定的队列中。
},
})

$.queue之前已经讲过了,可以看这个连接https://www.cnblogs.com/greatdesert/p/11670591.html,由于插入的是默认动画,因此插入到队列后就会自动执行的。

$.speed()是用于负责修正运行时间、缓动函数,重写完成回调函数的,如下:

jQuery.extend({
speed: function( speed, easing, fn ) { //负责修正运行时间、缓动函数,重写完成回调函数。//speed:表示动画持续时间、easing是所使用的缓动函数、fn是动画完成时被调用的函数。
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { //如果speed是对象,即$.fn.animate()只传入了两个参数的格式,则将该对象赋值给opt对象。
complete: fn || !fn && easing || //依次尝试把最后一个参数作为完成回调函数保存到complete中
jQuery.isFunction( speed ) && speed,
duration: speed, //动画持续时间
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing //如果参数3非空,则设置easing为参数2
}; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : //修正duration选项
opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; // normalize opt.queue - true/undefined/null -> "fx"
if ( opt.queue == null || opt.queue === true ) {
opt.queue = "fx";
} // Queueing
opt.old = opt.complete; //备份完成回调函数complete到old属性中 opt.complete = function( noUnmark ) { //重写complete函数,当前动画完成时,自动取出下一个动画函数。
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
} if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
} else if ( noUnmark !== false ) {
jQuery._unmark( this );
}
}; return opt; //最后返回opt
},
})

$.animate函数非常灵活,可以有如下形式:

  • animate(prop,speed,easing,callback)
  • animate(prop,easing,callback)
  • animate(prop,speed,callback)
  • animate(prop,callback)
  • animate(prop,obj)

这就是$.speed函数的功劳了。

回到$.animate函数内,当将doAnimation插入到队列后,由于是默认队列就会自动执行该函数,doAnimation实现如下:

function doAnimation() {                    //负责遍历动画样式集合,先修正、解析、备份样式名,然后为每个样式调用构造函数jQuery.fx()构造动画
// XXX 'this' does not always have a nodeName when running the
// test suite if ( optall.queue === false ) { //如果选项queue为false,动画将会立即执行
jQuery._mark( this );
} var opt = jQuery.extend( {}, optall ), //opt是完整选项集optall的副本;
isElement = this.nodeType === 1, //isElement用于检测当前元素是否是DOM元素
hidden = isElement && jQuery(this).is(":hidden"), //hidden表示当前元素是否是可见的
name, val, p, e,
parts, start, end, unit,
method; // will store per property easing and be used to determine when an animation is complete
opt.animatedProperties = {}; //存放动画样式的缓存函数,当动画完成后,属性值被设置为true。 for ( p in prop ) { //遍历动画样式集合prop。修正、解析、备份样式 ;p是每一个样式的名称,比如:width height // property name normalization
name = jQuery.camelCase( p ); ///将样式名格式化为驼峰式
if ( p !== name ) {
prop[ name ] = prop[ p ];
delete prop[ p ];
} val = prop[ name ]; //获取每个样式的结束值和缓动函数
if ( jQuery.isArray( val ) ) { //如果样式值是一个数组
opt.animatedProperties[ name ] = val[ 1 ]; //取第二个元素为该样式的缓动函数
val = prop[ name ] = val[ 0 ]; //修正第一个元素为该样式的结束值。
} else {
opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
} if ( val === "hide" && hidden || val === "show" && !hidden ) { //如果值是hide但当前元素不可见,或者值是show但当前元素可见,表示不需要执行动画就已经完成了
return opt.complete.call( this ); //直接触发完成回调函数。这是一个优化措施
} if ( isElement && ( name === "height" || name === "width" ) ) { //备份或修正可能影响动画效果的样式,这些样式将在动画完成后被恢复。
// Make sure that nothing sneaks out
// Record all 3 overflow attributes because IE does not
// change the overflow attribute when overflowX and
// overflowY are set to the same value
opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width
// animations on inline elements that are having width/height animated
if ( jQuery.css( this, "display" ) === "inline" &&
jQuery.css( this, "float" ) === "none" ) { // inline-level elements accept inline-block;
// block-level elements need to be inline with layout
if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
this.style.display = "inline-block"; } else {
this.style.zoom = 1;
}
}
}
} if ( opt.overflow != null ) { //如果opt.overflow不为空,即样式值是width或height、
this.style.overflow = "hidden"; //则设置样式overflow为'hidden',即超出部分隐藏,这样不影响其他布局
} for ( p in prop ) { //再次遍历动画样式集合prop,为每个属性执行动画效果 p是每个样式名,比如:width、height
e = new jQuery.fx( this, opt, p ); //调用构造函数jQuery.fn(elem,options,prop)创建一个标准动画对象
val = prop[ p ]; //获取设置的结束值,比如:500px,600px if ( rfxtypes.test( val ) ) { //如果样式值val是toggle、show或hide // Tracks whether to show or hide based on private
// data attached to the element
method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
if ( method ) {
jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
e[ method ]();
} else {
e[ val ]();
} } else { //如果样式值是数值型,则计算开始值start、结束值end、单位unit,然后开始执行动画
parts = rfxnum.exec( val ); //rfxnum用于匹配样式值中的运算符、数值、单位。rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, 格式:Array [ "700px", 符号, "数值", "单位" ] ,比如:'+=20px',匹配后:Array [ "+=20px", "+=", "20", "px" ]
start = e.cur(); //获取初始值,比如:200 if ( parts ) { //如果样式值能够匹配正则parts,则说明是数值型,就计算开始值start、结束值end、单位unit,然后开始执行动画。
end = parseFloat( parts[2] ); //end是传入的样式的数值,可能是结束值,也可能要加上或减去的值
unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); //unit是最后的单位,如果没有传入则设置为px单位 // We need to compute starting value
if ( unit !== "px" ) { //如果结束值的单位不是默认单位px,则将开始值start换算为与结束值end的单位一致的数值。
jQuery.style( this, p, (end || 1) + unit);
start = ( (end || 1) / e.cur() ) * start;
jQuery.style( this, p, start + unit);
} // If a +=/-= token was provided, we're doing a relative animation
if ( parts[1] ) { //如果样式值以+= 或 -=开头,则用开始值加上或减去给定的值,
end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; //得到结束值
} e.custom( start, end, unit ); //调用jQuery.fx.prototype.custom(from,to,unit)开始执行动画。start是开始值,end是结束值,unit是单位。 } else {
e.custom( start, val, "" ); //样式值不是数值型时,仍然开始执行动画,以支持插件扩展的动画样式。
}
}
} // For JS strict compliance
return true;
}

函数内首先调用new jQuery.fx()创建一个$.fx对象,最后调用该对象的custom开始执行动画,$.fx的定义如下:

jQuery.extend({
fx: function( elem, options, prop ) { //jQuery构造函数,用于为单个样式构造动画对象。elem:当前匹配元素、options:当前动画的选项集、prop:参与动画的当前样式。
this.options = options;
this.elem = elem;
this.prop = prop; options.orig = options.orig || {};
}
})

就是在当前实例上挂载几个属性,它的其它方法都定义在原型上的,例如cur和custom如下:

jQuery.fx.prototype = {
// Get the current size
cur: function() { //获取this.elem元素this.prop样式当前的值
if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { //如果当前DOM元素的this.prop属性不为空,且没有style属性或者有style属性但是style[this.prop]为null
return this.elem[ this.prop ]; //
} var parsed,
r = jQuery.css( this.elem, this.prop ); //获取该DOM元素的prop样式的值,比如:200px
// Empty strings, null, undefined and "auto" are converted to 0,
// complex values such as "rotate(1rad)" are returned as is,
// simple values such as "10px" are parsed to Float.
return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
}, // Start an animation from one number to another
custom: function( from, to, unit ) { //负责开始执行单个样式的动画。from:当前样式的开始值、to:当前样式的结束值、unit:当前样式的单位。
var self = this, //self是当前样式的jQuery.fx对象
fx = jQuery.fx; this.startTime = fxNow || createFxNow(); //动画开始的时间,值为当前时间,每次调用animate()方法时多个样式的该值是一样的。
this.end = to; //当前样式的结束值
this.now = this.start = from; //当前样式的单位。
this.pos = this.state = 0; //this.now:当前帧样式值 、this.start:当前样式的开始值
this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); //this.pos:差值百分比。this.state:已执行时间的百分比 function t( gotoEnd ) { //构造封装了jQuery.fx.prototype.step()的单帧闭包动画函数,函数t通过闭包机制引用jQuery.fx()对象。
return self.step( gotoEnd );
} t.queue = this.options.queue; //动画类型,默认为:fx
t.elem = this.elem; //elem是当前匹配元素
t.saveState = function() { //记录当前样式动画的状态,在stop()中用的到
if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
jQuery._data( self.elem, "fxshow" + self.prop, self.start );
}
}; if ( t() && jQuery.timers.push(t) && !timerId ) { //执行单帧闭包动画函数t(gotoEnd),如果该函数返回true则表示动画尚未完成,则并将其插入全局动画函数数组jQuery.timers中,如果timeId不存在,timeId是执行动画的全局定时器。
timerId = setInterval( fx.tick, fx.interval ); //创建一个定时器。周期性的执行jQuery.fx.tick()方法,从而开始执行动画。
}
},

$.tick()内会执行setInterval,隔13毫秒就执行一次 t() 函数,t函数内会执行step()函数,该函数会计算匹配元素下一次的样式值,如下:

writer by:大沙漠 QQ:22969969

jQuery.fx.prototype = {
step: function( gotoEnd ) { //负责计算和执行当前样式动画的一帧。
var p, n, complete,
t = fxNow || createFxNow(), //t是当前时间
done = true,
elem = this.elem, //匹配元素
options = this.options; //选项 if ( gotoEnd || t >= options.duration + this.startTime ) { //动画已完成的逻辑 如果参数totoEnd为true(stop())调用时),或超出了动画完成时间,最后会返回false
this.now = this.end; //设置当前帧式值为结束值
this.pos = this.state = 1; //设置已执行时间百分比和差值百分比为1
this.update(); //调用update()更新样式值。这三行代码是用来修正动画可能导致的样式误差的。 options.animatedProperties[ this.prop ] = true; //设置options.animatedProperties[ this.prop ]为true,表示当前样式动画已经执行完成。 for ( p in options.animatedProperties ) {
if ( options.animatedProperties[ p ] !== true ) {
done = false;
}
} if ( done ) {
// Reset the overflow
if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { jQuery.each( [ "", "X", "Y" ], function( index, value ) {
elem.style[ "overflow" + value ] = options.overflow[ index ];
});
} // Hide the element if the "hide" operation was done
if ( options.hide ) {
jQuery( elem ).hide();
} // Reset the properties, if the item has been hidden or shown
if ( options.hide || options.show ) {
for ( p in options.animatedProperties ) {
jQuery.style( elem, p, options.orig[ p ] );
jQuery.removeData( elem, "fxshow" + p, true );
// Toggle data is no longer needed
jQuery.removeData( elem, "toggle" + p, true );
}
} // Execute the complete function
// in the event that the complete function throws an exception
// we must ensure it won't be called twice. #5684 complete = options.complete;
if ( complete ) { options.complete = false;
complete.call( elem );
}
} return false; } else { //如果当前样式动画尚未完成
// classical easing cannot be used with an Infinity duration
if ( options.duration == Infinity ) {
this.now = t;
} else {
n = t - this.startTime; //n = 当前时间-开始时间 = 已执行时间
this.state = n / options.duration; //已执行时间百分比 = 已执行时间/运行时间 // Perform the easing function, defaults to swing
this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); //差值百分比=缓动函数(已执行时间百分比,已执行时间,0,1,运行时间)
this.now = this.start + ( (this.end - this.start) * this.pos ); //当前帧样式值=开始值+差值百分比*(结束值-开始值)
}
// Perform the next step of the animation
this.update(); //更新样式值。
} return true;
}
}

计算出样式值后会将值保存到this.now上面,最终调用this.update()进行更新样式,update源码如下:

jQuery.fx.prototype = {
update: function() { //更新当前样式的值。内部通过jQuery.fx.step中的方法来更新样式的值
if ( this.options.step ) { //如果当前的animate指定了step回调函数
this.options.step.call( this.elem, this.now, this ); //则调用该回调函数,参数分别是当前样式的值和元素的引用
} ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); //否则调用默认方法jQuery.fx.step._default()直接设置内联样式(style属性)或DOM属性。
},
}

可以看到优先通过$.fx.step来进行修改样式,其次通过$.fx.step._default来修改,如下:

jQuery.extend( jQuery.fx, {
step: { //最后的步骤:更新样式
opacity: function( fx ) { //修正样式opacity的设置行为
jQuery.style( fx.elem, "opacity", fx.now );
}, _default: function( fx ) { //默认情况下,通过直接设置内联属性(style属性)或DOM属性更新样式值。
if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { //如果当前元素有style属性,并且syle含有指定的样式
fx.elem.style[ fx.prop ] = fx.now + fx.unit; //在style属性上设置样式值
} else {
fx.elem[ fx.prop ] = fx.now; //否则在DOM元素上设置DOM属性,例如:scrollTop、scrollLeft
}
}
}
})

可以看到最终就是修改的元素的style来修改样式的,或者直接修改DOM对象属性。

jQuery 源码解析(三十) 动画模块 $.animate()详解的更多相关文章

  1. jQuery 源码解析(三十一) 动画模块 便捷动画详解

    jquery在$.animate()这个接口上又封装了几个API,用于进行匹配元素的便捷动画,如下: $(selector).show(speed,easing,callback)        ;如 ...

  2. jQuery 源码解析(二十五) DOM操作模块 html和text方法的区别

    html和text都可以获取和修改DOM节点里的内容,方法如下: html(value)     ;获取匹配元素集合中的一个元素的innerHTML内容,或者设置每个元素的innerHTML内容,   ...

  3. jQuery 源码解析(二十六) 样式操作模块 样式详解

    样式操作模块可用于管理DOM元素的样式.坐标和尺寸,本节讲解一下样式相关,样式操作通过jQuery实例的css方法来实现,该方法有很多的执行方法,如下: css(obj)            ;参数 ...

  4. jQuery 源码解析(三) pushStack方法 详解

    该函数用于创建一个新的jQuery对象,然后将一个DOM元素集合加入到jQuery栈中,最后返回该jQuery对象,有三个参数,如下: elems Array类型 将要压入 jQuery 栈的数组元素 ...

  5. jQuery 源码解析(二十九) 样式操作模块 尺寸详解

    样式操作模块可用于管理DOM元素的样式.坐标和尺寸,本节讲解一下尺寸这一块 jQuery通过样式操作模块里的尺寸相关的API可以很方便的获取一个元素的宽度.高度,而且可以很方便的区分padding.b ...

  6. JQuery源码解析(十)

    默认回调对象设计 不传入任何参数,调用add的时候将函数add到内部的list中,调用fire的时候顺序触发list中的回调函数: function fn1(val) { console.log('f ...

  7. jQuery 源码解析(二十八) 样式操作模块 scrollLeft和scrollTop详解

    scrollLeft和scrollTop用于获取/设置滚动条的,如下: scrollLeft(val) ;读取或设置整个页面的水平滚动条距离 scrollTop(val) ;读取或设置整个页面的垂直滚 ...

  8. jQuery 源码解析(二十四) DOM操作模块 包裹元素 详解

    本节说一下DOM操作模块里的包裹元素子模块,该模块可将当前匹配的元素替换指定的DOM元素,有如下方法: wrap(html)               ;在每个匹配元素的外层添加一层DOM元素   ...

  9. jQuery 源码解析(二十二) DOM操作模块 复制元素 详解

    本节说一下DOM操作模块里的复制元素子模块,该模块可以复制一个DOM节点,并且可选择的设置是否复制其数据缓存对象(包含事件信息)和是否深度复制(子孙节点等),API如下: $.clone(elem, ...

随机推荐

  1. 844. 走迷宫(bfs模板)

    给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0或1,其中0表示可以走的路,1表示不可通过的墙壁. 最初,有一个人位于左上角(1, 1)处,已知该人每次可以向上.下.左.右任意一个方向移 ...

  2. Bootstrap 警告框(Alert)插件

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. VS Code中Ionic serve命令 执行跳出的问题

    项目情况:用vscode编写的ionic(tab类型)项目(具体使用到的技术Angular\Typescrip\Ionic) 具体情况如下: 找到的可能原因: 出错的项目情况:在一个ts文件中编写两个 ...

  4. Codeforces1295D. Same GCDs (欧拉函数)

    https://codeforces.com/contest/1295/problem/D 设gcd(a,m)= n,那么找gcd(a +x ,m)= n个数,其实就等于找gcd((a+x)/n,m/ ...

  5. Base64编码和其在图片的传输的应用

    Base64 [原文链接] 目前Base64已经成为网络上常见的传输8Bit字节代码的编码方式之一.做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后再进行签名或加密,之后再进 ...

  6. arm-linux-gcc

    搭建交叉编译环境,即安装.配置交叉编译工具链.在Ubuntu环境下编译出嵌入式Linux系统所需的操作系统.应用程序等,然后再上传到目标机上. 交叉编译工具链是为了编译.链接.处理和调试跨平台体系结构 ...

  7. Python模块/包/库安装几种方法(转载)

    一.方法1: 单文件模块直接把文件拷贝到 $python_dir/Lib 二.方法2: 多文件模块,带setup.py 下载模块包(压缩文件zip或tar.gz),进行解压,CMD->cd进入模 ...

  8. 2020牛客竞赛 DP F 碎碎念

    作者:儒生雄才1链接:https://ac.nowcoder.com/discuss/366644来源:牛客网 题目连接:https://ac.nowcoder.com/acm/contest/300 ...

  9. python tkinter模版

    import tkinter import time import threading from tkinter import ttk event = threading.Event() once=0 ...

  10. 小匠第二周期打卡笔记-Task03

    一.过拟合欠拟合及其解决方案 知识点记录 模型选择.过拟合和欠拟合: 训练误差和泛化误差: 训练误差 :模型在训练数据集上表现出的误差, 泛化误差 : 模型在任意一个测试数据样本上表现出的误差的期望, ...