1 /**
2 * Unslider by @idiot and @damirfoy
3 * Contributors:
4 * - @ShamoX
5 *
6 */
7
8 (function($, f) {
9 var Unslider = function() {
10 // 克隆對象
11 var _ = this;
12
13 // 設置一些默認參數
14 _.o = {
15 speed: 500, // 動畫過渡的速度(毫秒),若是不須要過渡效果就設置爲false
16 delay: 3000, // 每張幻燈片的間隔時間(毫秒), 若是不是自動播放就設置爲false
17 init: 0, // 初始化延遲時間(毫秒),若是不須要延遲就設置爲false
18 pause: !f, // 當鼠標指針浮動在當前區域內時是否暫停自動播放
19 loop: !f, // 是否無盡循環播放
20 keys: f, // 是否開啓鍵盤導航
21 dots: f, // 是否顯示導航點
22 arrows: f, // 是否顯示向前和向後的箭頭
23 prev: '←', // 向前按鈕中的顯示文字(或html片斷)
24 next: '→', // 向後......
25 fluid: f, // 是否寬度自適應
26 starting: f, // 在每一個動畫前調用的函數
27 complete: f, // 在每一個動畫以後調用的函數
28 items: '>ul', // 幻燈片的容器選擇器
29 item: '>li', // 須要滾動的選擇器
30 easing: 'swing',// 動畫的緩動函數(easing function)
31 autoplay: true // 是否容許自動播放
32 };
33
34 _.init = function(el, o) {
35 // 將咱們在外部調用時設置的參數覆蓋掉默認參數
36 _.o = $.extend(_.o, o);
37
38 _.el = el;
39 _.ul = el.find(_.o.items);//返回ul元素集合
40 _.max = [el.outerWidth() | 0, el.outerHeight() | 0];//保存一下幻燈片div容器的寬和高
41 _.li = _.ul.find(_.o.item).each(function(index) {
42 var me = $(this),
43 width = me.outerWidth(),
44 height = me.outerHeight();
45
46 // 記錄最大幻燈片的寬高
47 if (width > _.max[0]) _.max[0] = width;
48 if (height > _.max[1]) _.max[1] = height;
49 });
50
51
52 // 申請一些臨時變量
53 var o = _.o,
54 ul = _.ul,
55 li = _.li,
56 len = li.length;//li元素個數
57
58 // 當前索引,或者叫頁碼更容易理解吧,源代碼中寫了「Current indeed」,應該是「index」吧
59 _.i = 0;
60
61 // 設置幻燈片div容器的樣式,高度初始化爲第一個li的高度
62 el.css({width: _.max[0], height: li.first().outerHeight(), overflow: 'hidden'});
63
64 // 設置ul元素的位置和寬度,寬度的公式是(li元素的個數乘以100)%,個人例子中就是300%
65 ul.css({position: 'relative', left: 0, width: (len * 100) + '%'});
66 if(o.fluid) {
67 li.css({'float': 'left', width: (100 / len) + '%'});//自適應寬度時,li元素的寬度就是把ul的寬度平均分紅len份
68 } else {
69 li.css({'float': 'left', width: (_.max[0]) + 'px'});//不是自適應時,li元素的寬度是最大的幻燈片的寬度
70 }
71
72 // 在init毫秒後開啓自動播放
73 o.autoplay && setTimeout(function() {
74 if (o.delay | 0) {
75 _.play();
76
77 if (o.pause) {
78 el.on('mouseover mouseout', function(e) {
79 _.stop();//鼠標通過時暫停
80 e.type == 'mouseout' && _.play();//鼠標離開時播放
81 });
82 };
83 };
84 }, o.init | 0);
85
86 // 鍵盤事件處理
87 if (o.keys) {
88 $(document).keydown(function(e) {
89 var key = e.which;
90
91 if (key == 37)
92 _.prev(); // 左箭頭按鍵
93 else if (key == 39)
94 _.next(); // 右箭頭按鍵
95 else if (key == 27)
96 _.stop(); // Esc
97 });
98 };
99
100 // 顯示導航點
101 o.dots && nav('dot');
102
103 // 顯示箭頭
104 o.arrows && nav('arrow');
105
106 // 使幻燈片div容器寬度自適應
107 if (o.fluid) {
108 $(window).resize(function() {
109 _.r && clearTimeout(_.r);
110
111 _.r = setTimeout(function() {
112 var styl = {height: li.eq(_.i).outerHeight()},
113 width = el.outerWidth();
114
115 ul.css(styl);
116 //這一串真是繞,其實就是計算div佔父窗口的寬度原始比例,而後記錄到styl中
117 styl['width'] = Math.min(Math.round((width / el.parent().width()) * 100), 100) + '%';
118 el.css(styl);//從新設置幻燈片div容器的寬度爲比例而不是像素值,這樣就能達到自適應的目的了
119 li.css({ width: width + 'px' });//設置li的絕對寬度,以防因div被自適應了而擠壓或拉伸了li形成內容扭曲(若有誤請大神指教)
120 }, 50);//每次父窗口改變大小時,幻燈片div容器延遲50毫秒後再跟着自適應大小,請大神告訴我這樣作的目的僅僅是爲了效果更天然麼
121 }).resize();//強制執行resize事件,使得自適應特性在最開始時就被設置好了
122 };
123
124 // 自定義move事件,這一段不太懂,求大神指點
125 if ($.event.special['move'] || $.Event('move')) {
126 // 爲幻燈片div元素綁定movestart、move、moveend事件
127 el.on('movestart', function(e) {
128 if ((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) {
129 e.preventDefault();//鼠標位置不在當前區域時取消事件的默認動做(我猜的,關鍵是不知道distX這幾個的準確含義)
130 }else{
131 el.data("left", _.ul.offset().left / el.width() * 100);
132 }
133 }).on('move', function(e) {
134 var left = 100 * e.distX / el.width();
135 var leftDelta = 100 * e.deltaX / el.width();
136 _.ul[0].style.left = parseInt(_.ul[0].style.left.replace("%", ""))+leftDelta+"%";
137
138 _.ul.data("left", left);
139 }).on('moveend', function(e) {
140 var left = _.ul.data("left");//
141 if (Math.abs(left) > 30){
142 var i = left > 0 ? _.i-1 : _.i+1;
143 if (i < 0 || i >= len) i = _.i;
144 _.to(i);
145 }else{
146 _.to(_.i);
147 }
148 });
149 };
150
151 return _;
152 };
153
154 // 播放指定索引的幻燈片
155 _.to = function(index, callback) {
156 if (_.t) {
157 _.stop();
158 _.play();
159 }
160 var o = _.o,
161 el = _.el,
162 ul = _.ul,
163 li = _.li,
164 current = _.i,
165 target = li.eq(index);
166 //在動畫以前執行的函數,個人例子裏都沒有,能夠忽略它們
167 $.isFunction(o.starting) && !callback && o.starting(el, li.eq(current));
168
169 // 若是(一張幻燈片也沒有或者索引無效),而且不是循環播放,就啥也不作,我以爲這樣很差,由於to這個函數就只能在循環播放狀態下工做了
170 if ((!target.length || index < 0) && o.loop == f) return;
171
172 // 檢查索引是否有效,超出時設置爲0,即第一張幻燈片
173 if (!target.length) index = 0;
174 if (index < 0) index = li.length - 1;//索引負數時設置爲最後一張幻燈片
175 target = li.eq(index);//獲取目標元素
176
177 var speed = callback ? 5 : o.speed | 0,//執行回調函數後返回的是真則speed設爲5,若是沒有回調函數或返回假則設置爲o.speed
178 easing = o.easing,
179 obj = {height: target.outerHeight()};
180
181 if (!ul.queue('fx').length) {//確保沒有爲ul元素添加函數隊列,應該是爲了防止上一次動做尚未完成吧
182 // 設置對應導航點的高亮
183 el.find('.dot').eq(index).addClass('active').siblings().removeClass('active');
184 // 改變幻燈片div容器的高度爲目標元素的高度,並把ul的位置向左移動(index*100%),使目標元素正好在幻燈片div容器區域
185 el.animate(obj, speed, easing) && ul.animate($.extend({left: '-' + index + '00%'}, obj), speed, easing, function(data) {
186 _.i = index;//移動結束以後更新一下當前索引
187 //動畫結束以後執行的函數,個人例子中也沒有,忽略它們
188 $.isFunction(o.complete) && !callback && o.complete(el, target);
189 });
190 };
191 };
192
193 // 每隔delay毫秒自動播放
194 _.play = function() {
195 _.t = setInterval(function() {
196 _.to(_.i + 1);//這裏就加了1個索引號,具體的處理都封裝在了to方法中
197 }, _.o.delay | 0);
198 };
199
200 // 中止自動播放
201 _.stop = function() {
202 _.t = clearInterval(_.t);
203 return _;
204 };
205
206 // 向後翻一張
207 _.next = function() {
208 return _.stop().to(_.i + 1);
209 };
210 // 向前翻一張
211 _.prev = function() {
212 return _.stop().to(_.i - 1);
213 };
214
215 // 建立導航點和箭頭
216 function nav(name, html) {
217 if (name == 'dot') {
218 html = '<ol class="dots">';
219 $.each(_.li, function(index) {
220 html += '<li class="' + (index == _.i ? name + ' active' : name) + '">' + ++index + '</li>';
221 });
222 html += '</ol>';
223 /*整理一下,在個人例子中就是這副摸樣
224 <ol class="dots">
225 <li class="dot active">0</li>
226 <li class="dot">1</li>
227 <li class="dot">2</li>
228 </ol>
229 */
230 } else {
231 html = '<div class="';
232 html = html + name + 's">' + html + name + ' prev">' + _.o.prev + '</div>' + html + name + ' next">' + _.o.next + '</div></div>';
233 /*也整理一下
234 <div class="arrows">
235 <div class="arrow prev">←</div>
236 <div class="arrow next">→</div>
237 </div>
238 */
239 };
240 //先給幻燈片div容器元素加上has-dots或arrows的class,再把上面組織好的元素追加爲子元素,並給該子元素添加click事件處理函數
241 _.el.addClass('has-' + name + 's').append(html).find('.' + name).click(function() {
242 var me = $(this);
243 me.hasClass('dot') ? _.stop().to(me.index()) : me.hasClass('prev') ? _.prev() : _.next();
244 });
245 };
246 };
247
248 // 將unslider方法擴展到jQuery對象,使任意jQuery對象都可以直接訪問該方法,就像上面那樣:$('.banner').unslider();
249 $.fn.unslider = function(o) {
250 var len = this.length;
251
252 // 遍歷li元素集
253 return this.each(function(index) {
254 var me = $(this),
255 key = 'unslider' + (len > 1 ? '-' + ++index : ''),
256 instance = (new Unslider).init(me, o);
257
258 // 給div元素添加數據
259 me.data(key, instance).data('key', key);
260 });
261 };
262
263 Unslider.version = "1.0.0";
264 })(jQuery, false);