根據Bootstrap中文網的介紹,Unslider一個超小的 jQuery輪播(slider)插件,參照這個漢化版的介紹頁面,這個插件有你須要的優勢,可是本文是抱着學習的態度,學習如何實現輪播插件,因此有些細節可能有所忽略。css
參照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.js
和unslider.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);還加上next
和prev
元素,加上了nav
導航。閉包
ul
是相對於.banner
定位的,雖然寬度是大於100%,可是.banner
是不會被ul
撐開的;而在ul
上配置width
和left
參數,能夠控制顯示ul
的起始位置,left:-100%
至關於ul
向左飄過去了100%
,通俗點說:app
父元素.banner
只能讓ul
顯示一個身位,可是ul
膨脹了,實際它有4個身位,相對於.banner
定位,默認left:0%
時,
至關於顯示0-1
身位的ul
,爲了顯示第二個身位的ul
,就必須將ul
往左移,讓它顯示1-2
位置的ul
的,因此此時設置left: -100%
,
以此類推。
$.fn.unslider
方法$.fn.unslider
方法是在jQuery原型鏈定義的方法,jQuery對象天然可以調用這個方法。前面的例子中咱們是直接調用的,並無傳入參數,事實上$.fn.unslider
還能夠接收相似這樣的參數:$(".banner").unslider("fn:arg1,arg2")
。最終調用在某個位置定義的fn
函數,參數是arg1
和arg2
。dom
$.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
來完成。
$.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
會指向#id
DOM元素,固然目前$.Unslider
靜態方法是沒法被jQuery對象直接調用的。
$.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);
使用方式上可能就有點不一樣了。
$.Unslider
源碼分析//開始重要的源碼分析
$.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 }; ... };
初始化方法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
只是初始化過程當中的一個入口,它還須要其餘初始化方法來幫助完成其餘業務邏輯,包括setup
、initNav
、initArrows
、initKeys
、initInfinite
、calculateSlides
等方法。接下來會逐個分析它們。
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
的一部分。
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); }); }); });
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]) ); }); };
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.next
和self.prev
方法。
// 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
這個技巧,啊!
// 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
類,這個類的做用暫且忽略。
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; };
開始定時器。
self.stop = function() { clearTimeout(self.interval); return self; };
清除定時器。
self.next = function() { //下一個 var target = self.current + 1; // 若是大於總數,就回到開始 if(target >= self.total) { target = 0; } //交給self.animate方法去完成 return self.animate(target, 'next'); };
self.prev = function() { return self.animate(self.current - 1, 'prev'); };
和self.next()方法類是,self.animate方法可以支持`animate(-1, 'prev')的寫法,不須要出入target參數。
雖然方法名叫animate
可是其實並無真正動起來,最終仍是交給三種不一樣輪播效果的animate
開頭的函數,如animateHorizontal
、animateVertical
和animateFade
。
// 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; };
這個方法會修改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.$nav
和self.$slides
都有調用$.fn._active
,這個類可以作到的是,將本身jQuery對象增長active類,並將全部兄弟元素對象移除active類。
這一版的unslider支持三種類型的動畫,左右、垂直方向輪播、還有就是fade(翻譯成閃現合理麼?),分別對應animateHorizotal
、animateVertical
和animateFade
三種方法。
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
參數被丟棄了(不明白)。
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); };
略
真正輪播頁面的方法。
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);
unslider自定義了幾種事件,包括unslider.change、unslider.ready和unslider.moved等,而在綁定導航元素的點擊事件時使用了命名空間的形式。命名空間由self.eventSuffix
指定。
請參考API文檔。
參考self.initNav
部分說明。
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
層。
$.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等參數
至此,整個輪播過程的調用過程就分析完畢。
略
略