jQuery源码分析系列(40): 动画设计

前言

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

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

换句话说,咱们能够把animate看做一个对象,对象封装本身的一系列属性与方法。html

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

 

动画的参数

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

咱们看看Animation方法的支持状况:ajax

.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接口等等promise

 

理解定义

<img id="book"  alt="" width="100" height="123"
  style="background:red;opacity:1;position: relative; left: 500px;" />

book.animate({
    opacity: 0.25,
    left: '50',
    height: 'toggle'
}, {
    duration :1000,
    specialEasing: {
        height: 'linear'
    },
    step: function(now, fx) {
        console.log('step')
    },
    progress:function(){
        console.log('progress')
    },
    complete:function(){
        console.log('动画完成')
    }
})

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

如上img元素的起始dom

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);
}

思路一实现:


 

思路二:动态计算

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

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

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

remaining

image

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

根据这个值,咱们能够得出当前位置的一个百分比了

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

pecent

image

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

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

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更优

思路二实现:


 

动画的扩展

知道动画处理的基本原理与算法了,那么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: '50',
      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对象

image

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

 

属性预处理

  • 针对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动画的模拟了,增强理解

相关文章
相关标签/搜索