unslider源碼分析

圖片描述
根據Bootstrap中文網的介紹,Unslider一個超小的 jQuery輪播(slider)插件,參照這個漢化版的介紹頁面,這個插件有你須要的優勢,可是本文是抱着學習的態度,學習如何實現輪播插件,因此有些細節可能有所忽略。css

1. 如何使用

參照Bootstrap中文網提供的介紹頁面,或者參照官網的介紹都是能夠,雖然unslider已經升級了版本,可是使用方式(API接口)仍是沒有改變。jquery

對於HTML結構的要求只須要提供相似如下結構便可:git

<div class="banner">
    <ul>
        <li>This is a slide.</li>
        <li>This is another slide.</li>
        <li>This is a final slide.</li>
    </ul>
</div>

而後引入jquery.jsunslider.js兩個文件,便可以在DOM加載完執行github

$(function() {
    $('.banner').unslider();
});

我取漢化版介紹頁面的元素,使用最新版的unslider.js,調用unslider(),比較頁面元素有什麼變化。正則表達式

源代碼數組

<div class="banner">
  <ul>
    <li style="background-image: url('img/sunset.jpg');">
      <div class="inner">
        <h1>The jQuery slider that just slides.</h1>
        <p>就是這個不到3kb的插件!沒有奇特的特效或無用的標籤。</p>

        <a class="btn" href="#download">下載</a>
      </div>
    </li>

    <li style="background-image: url('img/wood.jpg');">
      <div class="inner">
        <h1>Fluid, flexible, fantastically minimal.</h1>
        <p>Use any HTML in your slides, extend with CSS. You have full control.</p>

        <a class="btn" href="#download">下載</a>
      </div>
    </li>

    <li style="background-image: url('img/subway.jpg');">
      <div class="inner">
        <h1>開源</h1>
        <p>Unslider的全部源碼都託管在GitHub上。</p>

        <a class="btn" href="//github.com/idiot/unslider">參與</a>
      </div>
    </li>

    <li style="background-image: url('img/shop.jpg');">
      <div class="inner">
        <h1>Uh, that’s about it.</h1>
        <p>I just wanted to show you another slide.</p>

        <a class="btn" href="#download">下載</a>
      </div>
    </li>
  </ul>
</div>

使用插件後的效果(有所節省)緩存

<div class="unslider">
    <div class="banner unslider-horizontal" style="overflow: hidden;">
    <ul class="unslider-wrap unslider-carousel" style="width: 400%; left: 0%;">
        <li style="width: 25%; class="unslider-active">
        </li>

        <li style="width: 25%; class="">
        </li>

        <li style="width: 25%; class="">
        </li>

        <li style="width: 25%; class="">
        </li>
    </ul>
    </div>
    <a class="unslider-arrow next">Next</a>
    <a class="unslider-arrow prev">Prev</a>
    <nav class="unslider-nav">
        <ol>
        <li data-slide="0" class="unslider-active">1</li>
        <li data-slide="1" class="">2</li>
        <li data-slide="2" class="">3</li>
        <li data-slide="3" class="">4</li>
        </ol>
    </nav>
</div>

能夠發現使用插件後,會在.banner上封裝<div class="unslider"></div>,而且對.banner設置樣式不讓子元素溢出;在ul上設置寬度是li元素的整數倍,li元素的全部兄弟元素平均結果(100/4);還加上nextprev元素,加上了nav導航。閉包

ul是相對於.banner定位的,雖然寬度是大於100%,可是.banner是不會被ul撐開的;而在ul上配置widthleft參數,能夠控制顯示ul的起始位置,left:-100%至關於ul向左飄過去了100%,通俗點說:app

父元素 .banner只能讓 ul顯示一個身位,可是 ul膨脹了,實際它有4個身位,相對於 .banner定位,默認 left:0%時,
至關於顯示 0-1身位的 ul,爲了顯示第二個身位的 ul,就必須將 ul往左移,讓它顯示 1-2位置的 ul的,因此此時設置 left: -100%
以此類推。

2. 使用 $.fn.unslider 方法

$.fn.unslider方法是在jQuery原型鏈定義的方法,jQuery對象天然可以調用這個方法。前面的例子中咱們是直接調用的,並無傳入參數,事實上$.fn.unslider還能夠接收相似這樣的參數:$(".banner").unslider("fn:arg1,arg2")。最終調用在某個位置定義的fn函數,參數是arg1arg2dom

2.1 分析 $.fn.unslider 源碼

//  And set up our jQuery plugin
$.fn.unslider = function(opts) {
  return this.each(function() {
    var $this = $(this);

    //  Allow usage of .unslider('function_name')
    //  as well as using .data('unslider') to access the
    //  main Unslider object
    if(typeof opts === 'string' && $this.data('unslider')) {
      opts = opts.split(':');

      var call = $this.data('unslider')[opts[0]];

      //  Do we have arguments to pass to the string-function?
      if($.isFunction(call)) {
        return call.apply($this, opts[1] ? opts[1].split(',') : null);
      }
    }

    return $this.data('unslider', new $.Unslider($this, opts));
  });
};

$.fn.unslider的重要邏輯都是在$.Unslider中實現的,第一次調用$.fn.unslider方法時將調用jQuery.data方法將新構造的$.Unslider實例保存到jQuery對象的緩存對象上,供後續使用;後續的調用能夠直接從這個jQuery緩存對象取出$.Unslider實例調用相關方法。這樣作的好處就是不會多執行$.Unslider構造方法?(好像是我本身編出來的一個理由)

jQuery插件通常最終都會在jQuery原型上定義要被jQuery對象調用的方法,或者經過直接定義的方式,如$.fn.myPlugin = function(){},或者首先定義好插件方法,而後經過$.fn.extend擴展方法將插件方法擴展到jQuery原型上。unslider插件經過了在jQuery定義靜態方法$.Unslider,而$.fn.unslider只是調用入口,全部的業務邏輯都能經過$.Unslider來完成。

3. $.Unslider

首先能夠把$.Unslider(context, options)看做構造函數,最終會被$.fn.unslider(options)調用。context參數是一個jQuery對象,對應要生成輪播效果的$('.banner')集合的某個元素的jQuery對象,即$($('.banner')[0]); options最終會被擴展到$.Unslider的默認參數中。

首先看$.Unslider內部對this的處理,內部會對this備份到self變量,後續的屬性和方法都在self基礎上定義。

$.Unslider = function(context, options) {
        var self = this;

        //  Create an Unslider reference we can use everywhere
        self._ = 'unslider';
        ...
}

個人理解,new $.Unslider的調用方法,在$.Unslider內部的this是指向$.Unslider對象本身的,若是是$('#id').Unslider()就不同了,此時this會指向#idDOM元素,固然目前$.Unslider靜態方法是沒法被jQuery對象直接調用的。

3.1 $.Unslider 總體結構

圖片描述

總體結構:

$.Unslider = function(context, options) {
    var self = this;
    
    //插件標識
    self._ = 'unslider';
    //默認參數
    self.defaults = {
        
    };
    /**
     *  參照生成後的頁面元素作個類比
     *  self.$parent => <div class="unslider"></div>
     *  self.$context => <div class="banner"></div>
     *  self.$container => <ul class="unslider-wrap"></ul>
     *  self.slides =>     <li></li>
     */
    //備份jQuery對象
    self.$context = context;
    self.options = {};
    //容器的父層
    self.$parent = null;
    //輪播的容器jQuery,最終是self.$context的子元素的jQuery對象
    //$('.banner>ul')
    self.$container = null;
    //每一個輪播的頁面
    selft.$slides = null;
    //導航組
    self.$nav = null;
    //左右指示
    self.$arrows = [];
    
    //輪播頁面總數
    self.total = 0;
    //當前輪播頁面的序號
    self.current = 0;
    
    //前綴
    self.prefix = self._ + '-';
    //用於監聽事件的後綴,是監聽事件的命名空間
    self.eventSuffix = '.' + self.prefix + ~~(Math.random() * 2e3);
    
    //定時器
    self.interval = null;
    
    //初始化方法
    self.init = function() {
        self.options = $.extend({}, self.defaults, options);
        
        //self.$container
        //self.$slides
        
        self.setup();
        
        $.each(['nav', 'arrows', 'keys', 'infinite'], function(index, module) {
                self.options[module] && && self['init' + $._ucfirst(module)]();
            });   
        //self.initSwipe();
        
        self.options.autoplay && self.start();
        
        self.calculateSlides();
        
        self.$context.trigger(self._ + '.ready'); 
        return self.animate(self.options.index || self.current, 'init');
    }; //end of self.init
    
    self.setup = function() {
        //css    
    };
    
    
    self.calculateSlides = function() {
            self.total = self.$slides.length
            //set total height or width
        };
        
    self.start = function() {
            self.interval = setTimeout(function() {
                    self.next();
                }, self.options.delay);
            return self;
        };
    self.stop = function() {
            clearTimeout(self.interval);
            
            return self;
        };
    self.initNav = function() {
        
        };
    self.initArrows = function() {
        
        };
        
    self.initKeys = function() {
        
        };
    self.initSwipe = function() {
        
        };
    self.destroyArrows = function() {};
    self.destroySwipe = function() {};
    self.destroyKeys = function() {};
    
    self.setIndex  = function(to) {
        
        };
    self.animate = function(to, dir) {
        
        };
    self.next = function() {
        
        };
    self.prev = function() {
        
        };
    self.animateHorizontal = function(to) { };
    self.animateVertical = function(to) { };
    self.slide = function(prop, to) {
        
        }; 
    self.animateFade = function() {};
    self._move = function($el, obj, callback, speed) {}  ; 
    
    //最終調用init方法,返回self,見self.init定義
    return self.init(options);                
};

$.Unslider這個靜態方法外,unslider插件還在jQuery原型上定義輔助方法:

$.fn._active = function(className) {

};

$._ucfirst = function(str) {

};

$.fn._move = function() {

};

總體結構很是相似面向對象的作法,若是$.Unslider是一個類定義,而$.Unslider(context, options)就是構造函數,其餘self.開頭的屬性和方法就是這個類的成員變量和成員方法。

其實以_開頭的方法能夠理解成私有方法,unslider並不想把它暴露出去。事實上,$.Unslider的全部定義的方法都可以被外部調用,除非使用閉包的方式。

var Unslider = (function() {
        function init(context, options) {} //初始化方法
        function _move() {}
        function next() {
                //內部調用_move,可是總體沒有暴露_move方法
            }
        var defaults = {
            
            };
        return {
                init: init
                next: next
            };
    })();
$.fn.unslider = {};     
$.fn.extend($.fn.unslider, Unslider);

使用方式上可能就有點不一樣了。

3.2 $.Unslider 源碼分析

//開始重要的源碼分析

3.2.1 默認參數

$.Unslider = function(context, options) {
    var self = this;

    //  Create an Unslider reference we can use everywhere
    self._ = 'unslider';

    // 默認參數會被擴展到self.options
    // 最終會被外部傳入的options參數覆蓋,見self.init方法
    self.defaults = {
        // 是否自動開始
        autoplay: false,
        // 動畫間隔微秒
        delay: 3000,
        // 速度微秒
        speed: 750,

        //  An easing string to use. If you're using Velocity, use a
        //  Velocity string otherwise you can use jQuery/jQ UI options.
        easing: 'swing', // [.42, 0, .58, 1],
        
        // 鍵盤事件相關
        keys: {
            prev: 37,
            next: 39
        },
        
        // 是否須要設置導航,設置爲true在self.init方法中會調用initNav方法
        nav: true,

        // 上一個和下一個的指示元素
        // 默認參數擴展到self.options後
        // self.options["arrows"]能夠轉換爲true,在self.init方法中會調用initArrows方法
        arrows: {
            prev: '<a class="' + self._ + '-arrow prev">Prev</a>',
            next: '<a class="' + self._ + '-arrow next">Next</a>'
        },

        // 方向
        animation: 'horizontal',

        // 選擇器表達式
        selectors: {
            container: 'ul:first',
            slides: 'li'
        },

        //  Do you want to animate the heights of each slide as
        //  it moves
        animateHeight: false,

        //  Active class for the nav
        activeClass: self._ + '-active',

        //  Have swipe support?
        //  You can set this here with a boolean and always use
        //  initSwipe/destroySwipe later on.
        swipe: true
    };
    ...
};

3.2.2 init方法

初始化方法init是由構造方法在內部調用的,最終返回這個對象self

//  Get everything set up innit
self.init = function(options) {
    // 擴展合併外部傳入的參數和默認參數
    // 這種寫法不會破壞原來的self.defaults,擴展的結果都放在{}
    self.options = $.extend({}, self.defaults, options);

    // 對容器進行封裝,添加樣式目的是讓容器相對與父元素相對定位
    // 參照`unslider-wrap`這個類樣式
    self.$container = self.$context.find(self.options.selectors.container).addClass(self.prefix + 'wrap');
    // 備份保存全部的輪播頁面jQuery對象
    self.$slides = self.$container.children(self.options.selectors.slides);

    // 調用setup方法
    self.setup();

    // self.options合併後的選項
    // 若是存在相應的參數,且能轉換爲true,則調用相應的初始化方法
    $.each(['nav', 'arrows', 'keys', 'infinite'], function(index, module) {
        // $._ucfirst利用正則表達式將首字母轉換爲大寫
        self.options[module] && self['init' + $._ucfirst(module)]();
    });

    // 若是引入了jquery.event.move.js和jquery.event.swipe.js文件就執行
    // 和動畫相關的另一個實現方法,與jQuery.animate同等的velocity
    if(jQuery.event.special.swipe && self.options.swipe) {
        self.initSwipe();
    }

    // 是否自動開始
    self.options.autoplay && self.start();

    // 計算
    self.calculateSlides();

    // 觸發自定義的事件
    self.$context.trigger(self._ + '.ready');

    // 開始運動到指定序號的頁面
    return self.animate(self.options.index || self.current, 'init');
};

本文中沒有打算引入velocity,輪播效果最終由jQuery.animate來完成,這應該不阻礙對整個unslider插件代碼的梳理分析。

init只是初始化過程當中的一個入口,它還須要其餘初始化方法來幫助完成其餘業務邏輯,包括setupinitNavinitArrowsinitKeysinitInfinitecalculateSlides等方法。接下來會逐個分析它們。

3.2.3 setup方法

self.setup = function() {
    //給輪播容器的復層(.banner)作封裝
    self.$context.addClass(self.prefix + self.options.animation).wrap('<div class="' + self._ + '" />');
    //備份容器的父層,即剛纔的封裝層
    self.$parent = self.$context.parent('.' + self._);

    //  We need to manually check if the container is absolutely
    //  or relatively positioned
    var position = self.$context.css('position');

    //  If we don't already have a position set, we'll
    //  automatically set it ourselves
    if(position === 'static') {
        self.$context.css('position', 'relative');
    }

    self.$context.css('overflow', 'hidden');
};

setup方法主要目的是對.banner($context)作封裝,設置$context的樣式,若是事先沒有$context的position,就設置它相對定位position:relative,設置overflow:hidden,這樣只顯示ul的一部分。

3.2.4 initNav方法

self.initNav = function() {
    // HTML5到導航標籤
    var $nav = $('<nav class="' + self.prefix + 'nav"><ol /></nav>');

    // 遍歷輪播頁面對象
    self.$slides.each(function(key) {
        // 從元素的屬性或者序號中獲取
        var label = this.getAttribute('data-nav') || key + 1;

        // 是否執行回調函數,這塊不是很明白
        if($.isFunction(self.options.nav)) {
            label = self.options.nav.call(self.$slides.eq(key), key, label);
        }

        // 增長導航項
        $nav.children('ol').append('<li data-slide="' + key + '">' + label + '</li>');
    });
    
    //  插入到$context並保存起來
    self.$nav = $nav.insertAfter(self.$context);

    // 綁定監聽事件 self.eventSuffix是命名空間,實際監聽事件仍是`click`
    self.$nav.find('li').on('click' + self.eventSuffix, function() {
        //  Cache our link and set it to be active
        var $me = $(this).addClass(self.options.activeClass);

        //  Set the right active class, remove any other ones
        $me.siblings().removeClass(self.options.activeClass);

        // 輪播到某個頁面 參數是序號
        self.animate($me.attr('data-slide'));
    });
};

導航的這些DOM元素是在js代碼中生成的,若是但願本身定製的話,可能就必須設置self.options.nav=false了,而且爲導航元素綁定事件好比

$(function() {
    var slider = $('.banner').unslider({nav: false});
    var self = slider.data('unslider');
    $('.myNav > li').each(function(key) {
            $(this).on('click', function() {
                    var $me = $(this).addClass('activClass');
                    $me.siblings().removeClass('activeClass');
                    
                    //重要
                    self.animate(key);
                });
        });
    });

3.2.5 initArrows方法

self.initArrows = function() {
    //若是指定arrows是true,則從新對self.options.arrows賦值
    //弱類型語言就是隨意,啊!
    if(self.options.arrows === true) {
        self.options.arrows = self.defaults.arrows;
    }

    // self.defaults.arrows是默認設計好了arrows須要的元素的
    $.each(self.options.arrows, function(key, val) {
        // insertAfter返回是$(val)
        // 因此能夠直接push到self.$arrows
        // self.$arrows是以前定義好的空數組
        self.$arrows.push(
            $(val).insertAfter(self.$context).on('click' + self.eventSuffix, self[key])
        );
    });
};

3.2.6 initKeys方法

self.initKeys = function() {
    //默認參數self.defaults.keys === true並不能成立
    //這裏條件經過只能是外部傳入的參數覆蓋的
    //外部參數沒有覆蓋的狀況,後續依然使用默認的參數
    if(self.options.keys === true) {
        self.options.keys = self.defaults.keys;
    }

    //使用默認參數
    $(document).on('keyup' + self.eventSuffix, function(e) {
        $.each(self.options.keys, function(key, val) {
            if(e.which === val) {
                $.isFunction(self[key]) && self[key].call(self);
            }
        });
    });
};

按照默認的參數,最終綁定鍵盤事件的時候,咱們看到的是

$.each({prev: 37, next: 39}, function(key, val){
    ...
    $.isFunction(self[key]) && self[key].call(self);
});

最終調用的仍是self.nextself.prev方法。

3.2.7 initInfinite方法

//  Infinite scrolling is a massive pain in the arse
//  so we need to create a whole bloody function to set
//  it up. Argh.
self.initInfinite = function() {
    var pos = ['first', 'last'];

    $.each(pos, function(index, item) {
        self.$slides.push.apply(
            self.$slides,
            
            //  Exclude all cloned slides and call .first() or .last()
            //  depending on what `item` is.
            self.$slides.filter(':not(".' + self._ + '-clone")')[item]()

            //  Make a copy of it and identify it as a clone
            .clone().addClass(self._ + '-clone')

            //  Either insert before or after depending on whether we're
            //  the first or last clone
            ['insert' + (index === 0 ? 'After' : 'Before')](
                //  Return the other element in the position array
                //  if item = first, return "last"
                self.$slides[pos[~~!index]]()
            )
        );
    });
};

這個方法默認狀況下是不會被調用的,須要在外部傳入infinite參數纔會被調用,如

$(function() {
    $('.banner').unslider({infinite: true});
    });

逐行來閱讀這個方法的代碼:

1) 首先定義數組

var pos = ['first', 'last'];

2) 遍歷數組

$.each(pos, function(index, item) {
    
    });

3) 向self.$slides插入克隆的輪播頁面jQuery對象

self.$slides.push.apply(
    self.$slides,
    //clone jQuery object here
);

首先self.$slides是在self.init方法初始化的,self.$slides = self.$container.children(self.options.selectors.slides);

self.$slides是一個jQuery對象,爲了向self.$slides插入(克隆的輪播頁面的)jQuery對象,
借用了self.$slides的方法。這裏彷佛是能夠改爲:

self.$slides.push(
        //clone jQuery object here
    );

有待進一步驗證。

4) 過濾得到須要克隆的元素(的jQuery對象)

self.$slides.filter(':not(".' + self._ + '-clone")')[item]()

其中item即爲first或者last,第一次咱們須要克隆第一個,第二次咱們須要克隆最後一個;克隆第一個插入到self.$slides的最後位置,克隆最後一個插入到self.$slides的開頭位置。若是不加過濾的話,容易致使一個問題,可是第二次克隆時經過相似self.$slides.last()方法咱們獲取到的是第一次克隆的結果,因此unslider利用了self._ + 'clone'類作了區分。

5) 執行克隆並加上unslider-clone類

.clone().addClass(self._ + '-clone')

6) 執行插入

['insert' + (index === 0 ? 'After' : 'Before')](
    // relative jQuery object to insertBefor or insertAfter
)

首先判斷是須要執行insertAfter仍是insertBefore方法,接5)執行這個方法的是克隆後的jQuery對象,能夠理解下面的僞代碼:

var cloneJQ; 
cloneJQ.insertAfter(anotherJQ);

當index === 0時,執行第一次克隆從原來self.$slides的第一個克隆插入到self.$slides結尾的位置,因此第一次應該是執行insertAfter方法。

7) 找到相對的輪播頁面jQuery對象

self.$slides[pos[~~!index]]()

不論是執行insertAfter仍是insertBefore都是一個相對的jQuery對象;第一次克隆咱們須要插入的位置是結尾,第二次插入的位置是開頭。即

index:0 --> self.$slides.last()
index:1 --> slef.$slides.first()

再來看pos[~~!index],這個目的是從pos數組獲取某個元素,關鍵看~~!index的結果。舉些例子:

~~!0    //1
~~!1    //0
~~!2    //0
~~!-1   //0

這個技巧,啊!

3.2.8 calculateSlides方法

//  Set up the slide widths to animate with
//  so the box doesn't float over
self.calculateSlides = function() {
    self.total = self.$slides.length;

    //  Set the total width
    if(self.options.animation !== 'fade') {
        var prop = 'width';

        if(self.options.animation === 'vertical') {
            prop = 'height';
        }

        self.$container.css(prop, (self.total * 100) + '%').addClass(self.prefix + 'carousel');
        self.$slides.css(prop, (100 / self.total) + '%');
    }
};

判斷輪播的方向是垂直仍是水平,設置容器的高度或寬度是self.$slides個數的倍數;設置每一個輪播頁面元素的高度或者寬度,因爲是相對的,因此輪播頁面的高度或寬度理論是沒有改變的。

好比

//變化前
ul 100px
li 100px
共有4個li

//變化後
ul 100 X 4 = 400 px
li ul.width / 4 = 100 px

另外給容器設置了unslider-carousel類,這個類的做用暫且忽略。

3.2.9 start方法

self.start = function() {
    self.interval = setTimeout(function() {
        //  Move on to the next slide
        self.next();

        //  If we've got autoplay set up
        //  we don't need to keep starting
        //  the slider from within our timeout
        //  as .animate() calls it for us
    }, self.options.delay);

    return self;
};

開始定時器。

3.2.10 stop方法

self.stop = function() {
    clearTimeout(self.interval);

    return self;
};

清除定時器。

3.2.11 next方法

self.next = function() {
    //下一個
    var target = self.current + 1;

    // 若是大於總數,就回到開始
    if(target >= self.total) {
        target = 0;
    }
    //交給self.animate方法去完成
    return self.animate(target, 'next');
};

3.2.12 prev方法

self.prev = function() {
    return self.animate(self.current - 1, 'prev');
};

和self.next()方法類是,self.animate方法可以支持`animate(-1, 'prev')的寫法,不須要出入target參數。

3.2.13 animate方法

雖然方法名叫animate可是其實並無真正動起來,最終仍是交給三種不一樣輪播效果的animate開頭的函數,如animateHorizontalanimateVerticalanimateFade

//  Despite the name, this doesn't do any animation - since there's
//  now three different types of animation, we let this method delegate
//  to the right type, keeping the name for backwards compat.
self.animate = function(to, dir) {
    //  Animation shortcuts
    //  Instead of passing a number index, we can now
    //  use .data('unslider').animate('last');
    //  or .unslider('animate:last')
    //  to go to the very last slide
    if(to === 'first') to = 0;
    if(to === 'last') to = self.total;

    //  Don't animate if it's not a valid index
    if(isNaN(to)) {
        return self;
    }

    if(self.options.autoplay) {
        self.stop().start();
    }
    //設置了目標序號
    self.setIndex(to);

    //觸發unslider.change事件
    //我的以爲自定義的事件最好不要用.號分隔
    self.$context.trigger(self._ + '.change', [to, self.$slides.eq(to)]);

    //  Delegate the right method - everything's named consistently
    //  so we can assume it'll be called "animate" + 
    var fn = 'animate' + $._ucfirst(self.options.animation);

    //  Make sure it's a valid animation method, otherwise we'll get
    //  a load of bug reports that'll be really hard to report
    if($.isFunction(self[fn])) {
        //self.current已經在setIndex方法中修改了
        self[fn](self.current, dir); 
    }

    return self;
};

3.2.14 setIndex方法

這個方法會修改self.current屬性。

self.setIndex = function(to) {
    //處理負數的狀況
    if(to < 0) {
        to = self.total - 1;
    }
    //current不能超過self.total -1
    self.current = Math.min(Math.max(0, to), self.total - 1);
    //若是支持導航,須要將相應的導航元素設置active類
    if(self.options.nav) {
        self.$nav.find('[data-slide="' + self.current + '"]')._active(self.options.activeClass);
    }
    //設置選中的輪播頁面的active類
    self.$slides.eq(self.current)._active(self.options.activeClass);

    return self;
};

self.$navself.$slides都有調用$.fn._active,這個類可以作到的是,將本身jQuery對象增長active類,並將全部兄弟元素對象移除active類。

3.3 輪播動畫

這一版的unslider支持三種類型的動畫,左右、垂直方向輪播、還有就是fade(翻譯成閃現合理麼?),分別對應animateHorizotalanimateVerticalanimateFade三種方法。

3.3.1 animateHorizontal方法

self.animateHorizontal = function(to) {
    var prop = 'left';

    //  Add RTL support, slide the slider
    //  the other way if the site is right-to-left
    if(self.$context.attr('dir') === 'rtl') {
        prop = 'right';
    }
    //見前面self.initInfinite解釋
    //若是self.options.infinite是true,在開始和結束位置都會多增長克隆的頁面元素
    //因此這裏須要減去相應的寬度
    if(self.options.infinite) {
        //  So then we need to hide the first slide
        self.$container.css('margin-' + prop, '-100%');
    }
    //委託給slide方法,to是序號
    return self.slide(prop, to);
};

animateHorizontal是由animate調用的,原來的參數中animate(to, dir)to被修正爲目標序號並設置到self.current變量後,調用animateHorizontal方法傳入animateHorizontal(self.current,dir),到了這裏彷佛dir參數被丟棄了(不明白)。

3.3.2 animateVertical方法

self.animateVertical = function(to) {
    self.options.animateHeight = true;

    //  Normal infinite CSS fix doesn't work for
    //  vertical animation so we need to manually set it
    //  with pixels. Ah well.
    //減去自身的高度
    if(self.options.infinite) {
        self.$container.css('margin-top', -self.$slides.outerHeight());
    }

    return self.slide('top', to);
};

3.3.3 animateFade方法

3.3.4 slide方法

真正輪播頁面的方法。

self.slide = function(prop, to) {
    //  If we want to change the height of the slider
    //  to match the current slide, you can set
    //  {animateHeight: true}
    if(self.options.animateHeight) {
        self._move(self.$context, {height: self.$slides.eq(to).outerHeight()}, false);
    }

    //  For infinite sliding we add a dummy slide at the end and start
    //  of each slider to give the appearance of being infinite
    // 處理參數infinite是true的狀況
    if(self.options.infinite) {
        var dummy;

        //  Going backwards to last slide
        if(to === self.total - 1) {
            //  We're setting a dummy position and an actual one
            //  the dummy is what the index looks like
            //  (and what we'll silently update to afterwards),
            //  and the actual is what makes it not go backwards
            dummy = self.total - 3;
            to = -1;
        }

        //  Going forwards to first slide
        if(to === self.total - 2) {
            dummy = 0;
            to = self.total - 2;
        }

        //  If it's a number we can safely set it
        if(typeof dummy === 'number') {
            self.setIndex(dummy);

            //  Listen for when the slide's finished transitioning so
            //  we can silently move it into the right place and clear
            //  this whole mess up.
            self.$context.on(self._ + '.moved', function() {
                if(self.current === dummy) {
                    self.$container.css(prop, -(100 * dummy) + '%').off(self._ + '.moved');
                }
            });
        }
    }

    //  We need to create an object to store our property in
    //  since we don't know what it'll be.
    var obj = {};

    //  Manually create it here
    obj[prop] = -(100 * to) + '%';

    //  And animate using our newly-created object
    return self._move(self.$container, obj);
};

處理self.options.infinite參數爲true的狀況時,源碼中有些指定的數字,不知道是何依據。
內部定義的obj,最後傳給self._move方法,輪播功能進一步委託給self._move來完成。作下假定,若是咱們使用默認參數,即水平輪播,並假設須要輪播到第二個頁面,此時後面的代碼最終效果以下:

var obj = {};
obj["left"] = -(100 * 1) + '%';
return self._move(self.$container, obj);

3.4 事件綁定

unslider自定義了幾種事件,包括unslider.change、unslider.ready和unslider.moved等,而在綁定導航元素的點擊事件時使用了命名空間的形式。命名空間由self.eventSuffix指定。

3.4.1 unslider自定義事件

請參考API文檔。

3.4.3 命名空間的click事件

參考self.initNav部分說明。

3.5 其餘方法

3.5.1 $.Unslider._move方法

self._move = function($el, obj, callback, speed) {
    //回調處理
    if(callback !== false) {
        callback = function() {
            self.$context.trigger(self._ + '.moved');
        };
    }
    //調用$.fn._move方法
    return $el._move(obj, speed || self.options.speed, self.options.easing, callback);
};

$el是有animateHorizontal方法調用self._move傳入的self.$container,即對應的ul層。

3.5.2 $.fn._move方法

$.fn._move = function() {
    //中止全部動畫,參照jQuery的animate說明文檔
    this.stop(true, true);
    //若是沒有添加velocity支持,最終動畫仍是由$.fn.animate方法來完成
    return $.fn[$.fn.velocity ? 'velocity' : 'animate'].apply(this, arguments);
};

根據前面的說明,最終交由$.fn.animate方法來挖成動畫。按照以前的假設,此時這裏的效果以下面代碼所示:

var obj = {"left": "-100%"};
return $.fn.animate.apply(self.$container, arguments);
//arguments有obj, speed, callback等參數

至此,整個輪播過程的調用過程就分析完畢。

3.5.3 $.fn._active方法

3.5.4 $._ucfirst方法

相關文章
相關標籤/搜索