前言

jQuery动画是通过animate这个API设置执行的,其内部也是按照每一个animate的划分封装了各自动画组的行为,

包括数据过滤、缓动公式、一些动画默认参数的设置、元素状态的调整、事件的处理通知机制、执行等等

换句话说,我们可以把animate看作一个对象,对象封装自己的一系列属性与方法。

jQuery可以支持连续动画,那么animate与animate之间的切换就是通过队列.queue,这个之前就已经详细的解释过了

动画的参数

jQuery的内部的方法都是针对API的处理范围设计的

我们看看Animation方法的支持情况:

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

简单的来说,就是把一对的参数丢大animate方法里面,然后animate就开始执行你参数规定的动画了,

那么动画每执一次就会通过回调通知告诉开发者,具体有complete/done/fail/always/step接口等等

理解定义

<img id="book"  alt="" width="" height=""
style="background:red;opacity:1;position: relative; left: 500px;" /> book.animate({
opacity: 0.25,
left: '',
height: 'toggle'
}, {
duration :,
specialEasing: {
height: 'linear'
},
step: function(now, fx) {
console.log('step')
},
progress:function(){
console.log('progress')
},
complete:function(){
console.log('动画完成')
}
})

首先,动画的参数都是最终值都是相对数据

如上img元素的起始

opacity是1,那么通过动画改成成0.25

left是500,那么通过动画改成成50

height为'toggle' 意味着如果是隐藏与显示的自动切换

step:是针对opacity/left/height各自动画,每次改变通知三次

progress 是把opacity/left/height看成一组了,每次改变只通知一次

动画的原理

jQuery动画的原理还是很简单的,靠定时器不断的改变元素的属性

我们模拟下animate的大致实现

让元素执行一个2秒的动画到坐标为left 50的区域

animate({
    left: '50',
duration: '2000'
}

按照常规的思路,我们需要3个参数

  • 动画开始位置
  • 动画的结束位置
  • 动画的运行时间

思路一:等值变化

我们在animate内部还需要计算出当然元素的初始化布局的位置(比如500px),那么我们在2秒的时间内需变换成50px,也就是运行的路劲长就是500-50 = 450px

那么算法是不是呼之欲出了?

每毫秒移动的距离 pos = 450/2000 = 0.225px

每毫秒移动left  = 初始位置 (+/-) 每毫秒递增的距离(pos * 时间)

这样算法我们放到setInterval就会发现错的一塌糊涂,我们错最本质的东西:JS是单线程,定时器都是排队列的,理论上也达不到1ms绘制一次dom

所以每次产生的这个下一次绘制的时间差根本不是一个等比的,所以我们按照线性的等值递增是有误的

function animate(elem, options){
//动画初始值
var start = 500
//动画结束值
var end = options.left
//动画id
var timerId;
var createTime = function(){
return (+new Date)
}
var startTime = createTime();
//需要执行动画的长度
var anminLength = start - end;
//每13毫秒要跑的位置
var pos = anminLength/options.time * 13
var pre = start;
var newValue;
function tick(){
if(createTime() - startTime < options.time){
newValue = pre - pos
//动画执行
elem.style['left'] = newValue + 'px';
pre = newValue
}else{
//停止动画
clearInterval(timerId);
timerId = null;
console.log(newValue)
}
}
//开始执行动画
var timerId = setInterval(tick, 13);
}

思路一实现:


<!doctype html><img id="book" style="background:red;opacity:1;position: relative; left: 500px;" alt="" width="100" height="123" data-mce-style="background: red; opacity: 1; position: relative; left: 500px;" /><script type="text/javascript">

var book = document.getElementById('book')

animate(book, {
left: 50,
time: 2000
})

function animate(elem, options){
//动画初始值
var start = 500
//动画结束值
var end = options.left
//动画id
var timerId;

var createTime = function(){
return (+new Date)
}

var startTime = createTime();

//需要执行动画的长度
var anminLength = start - end;

//每13毫秒要跑的位置
var pos = anminLength/options.time * 13

var pre = start;
var newValue;

function tick(){
if(createTime() - startTime < options.time){
newValue = pre - pos
//动画执行
elem.style['left'] = newValue + 'px';
pre = newValue
}else{
//停止动画
clearInterval(timerId);
timerId = null;
console.log(newValue)
}
}

//开始执行动画
var timerId = setInterval(tick, 13);
}

</script>

思路二:动态计算

setInterval的调用是不规律的,但是调用的时间是(2秒)是固定的,我们可以在每次调用的时候算法时间差的比值,用这个比值去计算移动的距离就比较准确了

remaining = Math.max(0, startTime + duration - currentTime),

通过这个公司我们计算出,每次setInterval调用的时候,当前时间在总时间中的一个位置

remaining

看到没有,这个值其实很符合定时器的特性,也是一个没有规律的值

根据这个值,我们可以得出当前位置的一个百分比了

var remaining = Math.max(0, startTime + options.duration - createTime())
var temp = remaining / options.duration || 0;
var percent = 1 - temp;

pecent

那么这个移动的距离就很简单了

我把整个公式就直接列出来了

var createTime = function(){
return (+new Date)
}
//元素初始化位置
var startLeft = 500;
//元素终点位置
var endLeft = 50;
//动画运行时间
var duration = 2000;
//动画开始时间
var startTime = createTime(); function tick(){
//每次变化的时间
var remaining = Math.max(0, startTime + duration - createTime())
var temp = remaining / duration || 0;
var percent = 1 - temp;
//最终每次移动的left距离
var leftPos = (endLeft- startLeft) * percent +startLeft;
} //开始执行动画
setInterval(tick, 13);

leftPos就是每次移动的距离了,基本上比较准确了,事实上jQuery内部也就是这么干的

这里13代表了动画每秒运行帧数,默认是13毫秒。属性值越小,在速度较快的浏览器中(例如,Chrome),动画执行的越流畅,但是会影响程序的性能并且占用更多的 CPU 资源

在新的游览器中,我们都可以采用requestAnimationFrame更优

思路二实现:

<!doctype html><img id="book" style="background:red;opacity:1;position: relative; left: 500px;" alt="" width="100" height="123" data-mce-style="background: red; opacity: 1; position: relative; left: 500px;" /><script type="text/javascript">

var book = document.getElementById('book')

animate(book, {
left: 50,
duration: 2000
})

function animate(elem, options){
//动画初始值
var start = 500
//动画结束值
var end = options.left
//动画id
var timerId;
var createTime = function(){
return (+new Date)
}
//动画开始时间
var startTime = createTime();

function tick(){
//每次变化的时间
var remaining = Math.max(0, startTime + options.duration - createTime())
var temp = remaining / options.duration || 0;
var percent = 1 - temp;
var stop = function(){
//停止动画
clearInterval(timerId);
timerId = null;
}
var setStyle = function(value){
elem.style['left'] = value + 'px'
}
//移动的距离
var now = (end - start) * percent + start;
if(percent === 1){
setStyle(now)
stop();
}else{
setStyle(now)
}
}

//开始执行动画
var timerId = setInterval(tick, 13);
}
</script>

动画的扩展

知道动画处理的基本原理与算法了,那么jQuery在这个基础上封装扩展,让动画使用起来更灵活方便

我归纳有几点:

  • 参数的多形式传递
  • 基于promise的事件反馈
  • 增加属性的show/hide/toggle的快捷方式
  • 可以给css属性设置独立的缓动函数

基于promise的事件通知

得益于deferred的机制,可以让一个对象转化成带有promise的特性,实现了done/fail/always/progress等等一系列的事件反馈接口

这样的设计我们并不陌生在ready、ajax包括动画都是基于这样的异步模型的结构

deferred = jQuery.Deferred()
//生成一个动画对象了
animation = deferred.promise({}) //混入动画的属性与方法

那么这样操作的一个好处就是,可以把逻辑处理都放到一块

我们在代码的某一个环节针对特别的处理,需要临时改变一些东西,但是在之后我们希望又恢复原样,为了逻辑的清晰,我们可以引入deferred.alway方法

在某一个环节改了一个属性,然后注册到alway方法上一个完成的回调用来恢复,这样的逻辑块是很清晰的

style.overflow = "hidden";
anim.always(function() {
//完成后恢复溢出
style.overflow = opts.overflow[0];
style.overflowX = opts.overflow[1];
style.overflowY = opts.overflow[2];
});

增加属性的show/hide/toggle的快捷方式

指定中文参数是比较特殊的,这种方式也是jQuery自己扩展的行为,逻辑上也很容易处理

ook.animate({
left: '',
height:'hide'
},

height高度在动画结束之后隐藏元素,那么意味着元素本身的高度height也是需要改变的从初始的位置慢慢的递减到0然后隐藏起来

代码中有这么一段,针对hide的动作,我们在done之后会给元素直接隐藏起来

//目标是显示
if (hidden) {
jQuery(elem).show();
} else {
//目标是隐藏
anim.done(function() {
jQuery(elem).hide();
});
}

其实show与hide的流程是一样的,只是针对元素在初始与结束的一个状态的改变

css属性设置独立的缓动函数

在动画预初始化之后(为了支持动画,临时改变元素的一些属性与状态),我们就需要给每一个属性生成一个独立的缓动对象了createTween,主要用于封装这个动画的算法与执行的一些流程操作控制

//////////////////
//生成对应的缓动动画 //
//////////////////
createTween: function(prop, end) {
var tween = jQuery.Tween(elem, animation.opts, prop, end,
animation.opts.specialEasing[prop] || animation.opts.easing);
//加入到缓动队列
animation.tweens.push(tween);
return tween;
},

tween对象

通过这个结构大概就知道了,这个就是用于生成动画算法所需要的一些数据与算法的具体流程控制了

属性预处理

  • 针对height/width动画的时候,要先处理本身元素溢出
  • 针对height/width动画的时候,元素本身的inline状态处理

我们知道元素本身在布局的时候可以用很多属性对其设置,可是一旦进行动画化的话,某些属性的设置可能会对动画的执行产生副作用,所以针对这样的属性,jQuery直接在内部做了最优的处理

如果我们进行元素height/width变化的时候,比如height:1,这样的处理jQuery就需要针对元素做一些强制性的处理

1 添加overflow =“hidden”
2.如果设置了内联并且没有设置浮动 display = "inline-block";

因为内容溢出与内联元素在执行动画的时候,与这个height/width的逻辑是符合的

当然针对这样的修改jQuery非常巧妙了用到了deferred.always方法,我们在执行动画的时候,由于动画的需要改了原始的属性,但是动画在结束之后,我们还是需要还原成其状态

deferred量身定做的always方法,不管成功与失败都会执行这个复原的逻辑

//设置溢出隐藏
if (opts.overflow) {
style.overflow = "hidden";
anim.always(function() {
//完成后恢复溢出
style.overflow = opts.overflow[0];
style.overflowX = opts.overflow[1];
style.overflowY = opts.overflow[2];
});
}

总结

通过上面不难看出,jQuery动画其实原理上本身是不复杂的。量变产生质变,通过扩展大量的便捷方式加大了逻辑上的难度,但是从根本上来说:

主要包括:

  • 属性过滤specialEasing处理的propFilter方法
  • 通过Deferred生成流程控制体系
  • 通过defaultPrefilter方法对动画执行的临时修正
  • 通过createTween方法,生成动画的算法与流程控制器
  • 最后通过setInterval来控制每一个createTween对象的执行

大体上jQuery的动画就这么些内容,当然还有一些细节的话 遇到在提出来了,下章就会通过上面的这些处理,实现一个类jquery动画的模拟了,加强理解

jQuery源码分析系列(40): 动画设计的更多相关文章

  1. jQuery源码分析系列(39) : 动画队列

    data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保 ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  5. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  6. jQuery源码分析系列(36) : Ajax - 类型转化器

    什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的 ...

  7. jQuery源码分析系列(38) : 队列操作

    Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Qu ...

  8. jQuery源码分析系列 : 整体架构

    query这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! ...

  9. jQuery源码分析系列(37) : Ajax 总结

    综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery ...

随机推荐

  1. OpenCV(三) 之 基本数据结构 CvMat和 IplImage

    OpenCV(三) 之 基本数据结构 CvMat和 IplImage CvMat IplImage OpenCv中基本的数据类型 类型 参数 表示 CvPoint int x,y 像素点 CvPoin ...

  2. Hadoop 学习资料集锦

    Hadoop 资料 虾皮系列教程. Sqoop 资料 官方安装文档. 浪迹天涯博客. 瀚海星空博客. ……

  3. 解决svn uuid变更问题

    简介: 今天在snv根目录下重新定位上传的url,更改后出现如下错误 .可以看到,原来Repository创建者的uuid是前者,而现在我操作的是后者的uuid.因此,目前的操作办法是 使用相关命令更 ...

  4. 亲临现场不是梦,2017央视春晚推出VR直播

    自里约奥运会首次试水VR直播 后,用户开始关注这种观影方式,一瞬间VR直播开始流行.就在月初,江苏卫视宣布2017年跨年晚会将进行VR全景直播.当然,央视是绝对不会错过这中潮流方式. 据悉,央视201 ...

  5. C# 完整List例子

    C# List Following examples show how to create and manipulate with .NET strongly typed list List<T ...

  6. 客户端用javascript获取文件大小

    客户端用javascript获取文件大小 1 ie实现代码如下: <script type="text/javascript" language="javascri ...

  7. STM32之DAC君

    如花说得好:呃呃呃.是俗话说得好:有了ADC,怎可少了DAC..我觉得奇怪.今天我开头就直奔主题了.我想了想,总结了一句话:孙悟空纵然有七十二变.无论是变成猫也好,变成狗也罢.始终还是会变回他本身.所 ...

  8. Android :fragment介绍

    一.关于Fragmemt 1.Fragment(片段),主要是为了支持更多的动态和灵活的用户界面设计,如平板电脑.Fragment允许组合和交换用户界面组件,而不需要更改视图层次结构.通过把Activ ...

  9. C#按照指定长度分割中英文字符串

    最近有一个需求:玩家发的不同长度文字,需要自适应行数. 初步实现想法很简单,直接获取字符数均分行数,再利用string.substring()切割即可.但是显而易见,由于一般字体下,中文显示宽度一般是 ...

  10. 安装dubbo管理中心

    从http://pan.baidu.com/s/1dDlI7aL下载dubbo-admin-2.5.4.war包 将下载的包放在tomcat的webapps目录,启动tomcat自动解压该war包,然 ...