前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >unslider源码分析

unslider源码分析

作者头像
用户3579639
发布2018-10-22 14:52:56
1.9K0
发布2018-10-22 14:52:56
举报
图片描述
图片描述

根据Bootstrap中文网的介绍,Unslider一个超小的 jQuery轮播(slider)插件,参照这个汉化版的介绍页面,这个插件有你需要的优点,但是本文是抱着学习的态度,学习如何实现轮播插件,所以有些细节可能有所忽略。

1. 如何使用

参照Bootstrap中文网提供的介绍页面,或者参照官网的介绍都是可以,虽然unslider已经升级了版本,但是使用方式(API接口)还是没有改变。

对于HTML结构的要求只需要提供类似以下结构即可:

代码语言:javascript
复制
<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加载完执行

代码语言:javascript
复制
$(function() {
    $('.banner').unslider();
});

我取汉化版介绍页面的元素,使用最新版的unslider.js,调用unslider(),比较页面元素有什么变化。

源代码

代码语言:javascript
复制
<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>

使用插件后的效果(有所节省)

代码语言:javascript
复制
<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%,通俗点说:

父元素.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函数,参数是arg1arg2

2.1 分析 $.fn.unslider 源码

代码语言:javascript
复制
//  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基础上定义。

代码语言:javascript
复制
$.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 整体结构

图片描述
图片描述

整体结构:

代码语言:javascript
复制
$.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原型上定义辅助方法:

代码语言:javascript
复制
$.fn._active = function(className) {

};

$._ucfirst = function(str) {

};

$.fn._move = function() {

};

整体结构非常类似面向对象的做法,如果$.Unslider是一个类定义,而$.Unslider(context, options)就是构造函数,其他self.开头的属性和方法就是这个类的成员变量和成员方法。

其实以_开头的方法可以理解成私有方法,unslider并不想把它暴露出去。事实上,$.Unslider的所有定义的方法都能够被外部调用,除非使用闭包的方式。

代码语言:javascript
复制
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 默认参数
代码语言:javascript
复制
$.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

代码语言:javascript
复制
//  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方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
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了,并且为导航元素绑定事件比如

代码语言:javascript
复制
$(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方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
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);
            }
        });
    });
};

按照默认的参数,最终绑定键盘事件的时候,我们看到的是

代码语言:javascript
复制
$.each({prev: 37, next: 39}, function(key, val){
    ...
    $.isFunction(self[key]) && self[key].call(self);
});

最终调用的还是self.nextself.prev方法。

3.2.7 initInfinite方法
代码语言:javascript
复制
//  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参数才会被调用,如

代码语言:javascript
复制
$(function() {
    $('.banner').unslider({infinite: true});
    });

逐行来阅读这个方法的代码:

1) 首先定义数组

代码语言:javascript
复制
var pos = ['first', 'last'];

2) 遍历数组

代码语言:javascript
复制
$.each(pos, function(index, item) {
    
    });

3) 向self.$slides插入克隆的轮播页面jQuery对象

代码语言:javascript
复制
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的方法。这里似乎是可以改成:

代码语言:javascript
复制
self.$slides.push(
        //clone jQuery object here
    );

有待进一步验证。

4) 过滤获得需要克隆的元素(的jQuery对象)

代码语言:javascript
复制
self.$slides.filter(':not(".' + self._ + '-clone")')[item]()

其中item即为first或者last,第一次我们需要克隆第一个,第二次我们需要克隆最后一个;克隆第一个插入到self.$slides的最后位置,克隆最后一个插入到self.$slides的开头位置。如果不加过滤的话,容易导致一个问题,但是第二次克隆时通过类似self.$slides.last()方法我们获取到的是第一次克隆的结果,所以unslider利用了self._ + 'clone'类做了区分。

5) 执行克隆并加上unslider-clone类

代码语言:javascript
复制
.clone().addClass(self._ + '-clone')

6) 执行插入

代码语言:javascript
复制
['insert' + (index === 0 ? 'After' : 'Before')](
    // relative jQuery object to insertBefor or insertAfter
)

首先判断是需要执行insertAfter还是insertBefore方法,接5)执行这个方法的是克隆后的jQuery对象,可以理解下面的伪代码:

代码语言:javascript
复制
var cloneJQ; 
cloneJQ.insertAfter(anotherJQ);

当index === 0时,执行第一次克隆从原来self.$slides的第一个克隆插入到self.$slides结尾的位置,所以第一次应该是执行insertAfter方法。

7) 找到相对的轮播页面jQuery对象

代码语言:javascript
复制
self.$slides[pos[~~!index]]()

不管是执行insertAfter还是insertBefore都是一个相对的jQuery对象;第一次克隆我们需要插入的位置是结尾,第二次插入的位置是开头。即

代码语言:javascript
复制
index:0 --> self.$slides.last()
index:1 --> slef.$slides.first()

再来看pos[~~!index],这个目的是从pos数组获取某个元素,关键看~~!index的结果。举些例子:

代码语言:javascript
复制
~~!0    //1
~~!1    //0
~~!2    //0
~~!-1   //0

这个技巧,啊!

3.2.8 calculateSlides方法
代码语言:javascript
复制
//  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个数的倍数;设置每个轮播页面元素的高度或者宽度,由于是相对的,所以轮播页面的高度或宽度理论是没有改变的。

比如

代码语言:javascript
复制
//变化前
ul 100px
li 100px
共有4个li

//变化后
ul 100 X 4 = 400 px
li ul.width / 4 = 100 px

另外给容器设置了unslider-carousel类,这个类的作用暂且忽略。

3.2.9 start方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
self.stop = function() {
    clearTimeout(self.interval);

    return self;
};

清除定时器。

3.2.11 next方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
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

代码语言:javascript
复制
//  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属性。

代码语言:javascript
复制
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方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
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方法

真正轮播页面的方法。

代码语言:javascript
复制
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来完成。做下假定,如果我们使用默认参数,即水平轮播,并假设需要轮播到第二个页面,此时后面的代码最终效果如下:

代码语言:javascript
复制
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方法
代码语言:javascript
复制
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方法
代码语言:javascript
复制
$.fn._move = function() {
    //停止所有动画,参照jQuery的animate说明文档
    this.stop(true, true);
    //如果没有添加velocity支持,最终动画还是由$.fn.animate方法来完成
    return $.fn[$.fn.velocity ? 'velocity' : 'animate'].apply(this, arguments);
};

根据前面的说明,最终交由$.fn.animate方法来挖成动画。按照之前的假设,此时这里的效果如下面代码所示:

代码语言:javascript
复制
var obj = {"left": "-100%"};
return $.fn.animate.apply(self.$container, arguments);
//arguments有obj, speed, callback等参数

至此,整个轮播过程的调用过程就分析完毕。

3.5.3 $.fn._active方法

3.5.4 $._ucfirst方法

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 如何使用
  • 2. 使用 $.fn.unslider 方法
    • 2.1 分析 $.fn.unslider 源码
    • 3. $.Unslider
      • 3.1 $.Unslider 整体结构
        • 3.2 $.Unslider 源码分析
          • 3.2.1 默认参数
          • 3.2.2 init方法
          • 3.2.3 setup方法
          • 3.2.4 initNav方法
          • 3.2.5 initArrows方法
          • 3.2.6 initKeys方法
          • 3.2.7 initInfinite方法
          • 3.2.8 calculateSlides方法
          • 3.2.9 start方法
          • 3.2.10 stop方法
          • 3.2.11 next方法
          • 3.2.12 prev方法
          • 3.2.13 animate方法
          • 3.2.14 setIndex方法
        • 3.3 轮播动画
          • 3.3.1 animateHorizontal方法
          • 3.3.2 animateVertical方法
          • 3.3.3 animateFade方法
          • 3.3.4 slide方法
        • 3.4 事件绑定
          • 3.4.1 unslider自定义事件
          • 3.4.3 命名空间的click事件
        • 3.5 其他方法
          • 3.5.1 $.Unslider._move方法
          • 3.5.2 $.fn._move方法
          • 3.5.3 $.fn._active方法
          • 3.5.4 $._ucfirst方法
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档