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動畫的模擬了,增強理解

相關文章
相關標籤/搜索