根据Bootstrap中文网的介绍,Unslider一个超小的 jQuery轮播(slider)插件,参照这个汉化版的介绍页面,这个插件有你需要的优点,但是本文是抱着学习的态度,学习如何实现轮播插件,所以有些细节可能有所忽略。
1. 如何使用
参照Bootstrap中文网提供的介绍页面,或者参照官网的介绍都是可以,虽然unslider已经升级了版本,但是使用方式(API接口)还是没有改变。
对于HTML结构的要求只需要提供类似以下结构即可:
<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加载完执行
$(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%
,通俗点说:
父元素
.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
函数,参数是arg1
和arg2
。
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
会指向#id
DOM元素,当然目前$.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
只是初始化过程中的一个入口,它还需要其他初始化方法来帮助完成其他业务逻辑,包括setup
、initNav
、initArrows
、initKeys
、initInfinite
、calculateSlides
等方法。接下来会逐个分析它们。
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.next
和self.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
开头的函数,如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;
};
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.$nav
和self.$slides
都有调用$.fn._active
,这个类能够做到的是,将自己jQuery对象增加active类,并将所有兄弟元素对象移除active类。
3.3 轮播动画
这一版的unslider支持三种类型的动画,左右、垂直方向轮播、还有就是fade(翻译成闪现合理么?),分别对应animateHorizotal
、animateVertical
和animateFade
三种方法。
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方法
略