bootstrap插件学习-bootstrap.scrollspy.js

bootstrap插件学习-bootstrap.scrollspy.js

先看bootstrap.dropdown.js的结构

function ScrollSpy(){} //构造函数
ScrollSpy.prototype = {} //构造器的原型
$.fn.scrollspy = function ( option ){} //jQuery原型上的自定义方法
$.fn.scrollspy.Constructor = ScrollSpy // jQuery原型上的自定义方法
$.fn.scrollspy.defaults = {} //默认参数
$(function(){}) //初始化执行

HTML结构

<style type="text/css">
        .scrollspy-example { height: 200px; overflow: auto; position: relative;}
    </style>

<div id="navbarExample" class="navbar navbar-static">
    <div class="navbar-inner">
        <div class="container" style=" auto;">
            <a class="brand" href="#">项目名称</a>
            <ul class="nav">
                <li class="active">
                    <a href="#fat">红利</a>
                </li>
                <li>
                    <a href="#mdo">哈芬</a>
                </li>
            </ul>
        </div>
    </div>
</div>
<div class="scrollspy-example" data-offset="0" data-target="#navbarExample" data-spy="scroll">
    <h4 id="fat">红利</h4>
    <p>
        中方向美方承诺提高国有企业红利上缴比例,增加上缴利润的*国企和省级国企的数量,将国有资本经营预算纳入国家预算体系。还承诺,鼓励包括国有公司在内的上市公司增加红利支付。还承诺在信贷提供、税收优惠和监管政策等方面对各类所有制企业一视同仁。
        <br>
        <br>
        美方认为,提高国有企业分红比例带来的收入可用于资助*的社保和养老开支,从而有可能降低中国人大量储蓄的必要性,让他们提高消费支出,从而达到刺激中国内需的目的。
    </p>
    <h4 id="mdo">哈芬</h4>
    <p>
        据估,中国高铁槽道市场约十几亿元,德国哈芬占70%。业内人士称,中铁设计院的铁道图纸,直接指定使用哈芬,而非技术标准。哈芬在德国使用成本高昂的不锈钢,在中国则是碳钢。更有业内人士证实,目前中国高铁用的实为国内生产,原产几乎不足四分之一。(财新)
    </p>
    <h4 id="one">遛狗</h4>
    <p> 近日,拍摄于四川绵阳街头的一张照片引起热议,一辆在路上行驶的法院警车,车窗里伸出一个宠物狗的脑袋,四处张望。此情景被怀疑是公务人员私用警车带宠物狗兜风。经调查得知,这是我国新近引进的一批特殊品种警犬,为麻痹犯罪分子,故意化妆成宠物狗的样子。 </p>
    <h4 id="two">失踪</h4>
    <p> 4月25日,19岁的韩耀在云南省昆明市晋宁县晋城镇南门村鑫云冷库附近失踪。家属在寻找时,竟然发现这一区域已先后有8名青少年失踪,其中近一年内就有6人。有一名青年雷玉生就在此地的大街上被人拖进了一面包车,被扔进黑砖窑强迫劳动,后逃离黑砖窑重获*。 </p>
    <h4 id="three">耳光</h4>
    <p> 30多岁女人直接吐东西在刚扫过的地上,环卫大姐上去说了两句,结果挨了三巴掌三脚。见到被打的环卫大姐时,她精神不好,坐在凳子上不说话,左脸的伤痕还很显眼,工友在一旁照料她。2012年5月4日,浙江省,杭州市。 </p>
    <p> 尹大姐说:“小孩子都知道不能在街上乱吐。”那女人说:那不就是你们环卫应该做的事情吗?尹大姐说:难道我们环卫工人就低人一等吗?”话音刚落,“啪”“啪”“啪”三个巴掌落在尹大姐脸上。 </p>
</div>

我们从初始化开始

/*
  * 初始化执行
  * */
  $(function () {
    $('[data-spy="scroll"]').each(function () {
      var $spy = $(this)
      $spy.scrollspy($spy.data())
    })
  })

根据HTML结构,这个很简单,获取body对象,并执行jQuery原型上的方法scrollspy,其中$spy.data()中的值,前几次的源码分析,这个值已经很清楚了,它拥有{spy:'scroll',target:'#navbarExample',offset :0 },进入jQuery的原型方法

 /*
  * jQuery原型上的自定义方法
  * */
  $.fn.scrollspy = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('scrollspy')
        , options = typeof option == 'object' && option;
      if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))//实例化ScrollSpy
      if (typeof option == 'string') data[option]()//传入方法名,执行该方法
    })
  }

跟modal的原型源码差不多,如果$this.data()中没有scrollspy属性则实例化构造器,另外该方法支持传入方法名,执行拥有方法名的方法。下面是构造器

/*
  * 构造器
  * */
  function ScrollSpy( element, options) {
    var process = $.proxy(this.process, this)//给原型上的process方法在该作用域下取了个别称
      , $element = $(element).is('body') ? $(window) : $(element)
      , href
    //console.log($element);//window对象
    this.options = $.extend({}, $.fn.scrollspy.defaults, options)

    //console.log(this.options);
    this.$scrollElement = $element.on('scroll.scroll.data-api', process)//绑定scorll事件,执行process方法,返回window的jQuery对象

    this.selector = (this.options.target
      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^s]+$)/, '')) //strip for ie7
      || '') + ' .nav li > a' //获取头标题的各个a标签

    this.$body = $('body').on('click.scroll.data-api', this.selector, process)//给头标题的各个a标签绑定事件,以便点击后可以跳到指定的内容,返回body的jQuery对象
    this.refresh()
    this.process()//调用原型上的process方法
  }

构造器完成的事情主要有3件:1.给window对象绑定scroll事件,2.给body对象中的头标题中的a标签绑定click事件,3.初始化执行refresh()和process()方法。我执行初始化的步骤

进入refresh()方法。

/*
      * position() 方法返回匹配元素相对于父元素的位置(偏移)。该方法返回的对象包含两个整型属性:top 和 left,以像素计。
      * 获取各个标签对应内容的高度
       * */
    , refresh: function () {
        this.targets = this.$body
          .find(this.selector)//查找头标题的各个a标签
          .map(function () {
            var href = $(this).attr('href')
            return /^#w/.test(href) && $(href).length ? href : null
          })//得到一个object对象,根据index去获取其中类容,有点类数组
        this.offsets = $.map(this.targets, function (id) {
          return $(id).position().top//获取各个标签对应内容的高度
        })
      }

看基于jQuery编写的插件源码的时候,很多时候就是帮助自己提高对jQuery方法的认知。这个refresh方法将各个a标签的top值返回给对象的offsets属性。其中不乏用到map方法去执行遍历操作。进入process方法

/*
      * 逻辑控制
      * 初始化时,activeTarget为空,scrollTop为0,for循环的第二个判断条件scrollTop >= offsets[i]中将判断false,当i为0时
      * ,targets[0]为#fat时,满足for循环的前三个条件,进入activate方法,
      *
      * */
    , process: function () {
        var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
          , offsets = this.offsets //div内容的高度集合
          , targets = this.targets //div内容的id集合对象
          , activeTarget = this.activeTarget
          , i
for (i = offsets.length; i--;) {
          activeTarget != targets[i]
            && scrollTop >= offsets[i]
            && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
            && this.activate( targets[i] )
        }
      }

javascript在书写判断时,有很多比较简洁的写法,我讲有写的好的,那就是写的骚,对于这个方法里的for循环中的3个&&,如果前3个判断有一个是false的话,都无法进入activate这个方法。注释中,我已经讲了初始化的过程,下面我描述下点击事件中,它是如何工作的。以HTML结构为例。

初始化时,'红利'按钮变色,当点击'哈芬'按钮时,会触发之前绑定的click事件,根据通过命名锚直接跳到命名锚的链接,下面的想对应的信息发生改变,以'哈芬'为标题的信息引入眼帘,但是这里需要注意是,之前我们在window上绑定了scroll事件,下面信息内容的改变,导致了滚动条的移动,触发了window上的scroll事件,所以我们的一次点击,实际上触发了两次事件。

先从点击事件开始,进过初始化之后,activeTarget的值已经被改成#fat,因此点击事件在经过for循环时,是到不了activate这个方法的。大家可以断点调试,或者将源码中的this.$scrollElement = $element.on('scroll.scroll.data-api', process)改成this.$scrollElement = $element之后调试。scroll事件依旧执行process事件。看起来情况跟上次一样,但需要注意此时的scrollTop已经发生改变,满足条件之后,进入activate方法。

/*
      * 逻辑控制后续执行
      * 初始化时将activeTarget设成#fat,所以默认刷新时'红利'这块显示被点击。当我们点击'哈芬'时,通过命名锚直接跳到命名锚的链接
      * 注意其他有两个过程:1.点击'哈芬'按钮,2.滚动条运动,两者都触发了事件。最后都进入了process方法。
      *
      * */
    , activate: function (target) {
        var active
this.activeTarget = target//设置activeTarget属性

        this.$body
          .find(this.selector).parent('.active')
          .removeClass('active')//找到头标题中父节点拥有active类的a标签,并删除a标签的父节点上的active

        active = this.$body
          .find(this.selector + '[href="' + target + '"]')
          .parent('li')
          .addClass('active')//在头标题中,给拥有href='xx'属性的a标签的父节点加上active属性,返回li标签

        if ( active.parent('.dropdown-menu') )  {
          active.closest('li.dropdown').addClass('active')
        }
      }

这个方法主要是收尾工作。让被点击按钮变色,同时也能达到滑倒到响应内容时,对应的按钮变色。这里主要的比较方式是通过内容的top高度跟window的scrollTop想比较,达到某个内容的高度,则获得了该内容对应的a标签。

这里我们在谈一个问题,即为什么我第一次点击'哈芬'时,是先执行的锚跳转还是先执行绑定方法?其实这个问题在之前的测试中,已经得到证明先绑定方法,再执行锚跳转,大家可以在绑定事件中加入alert去调试。答案如我们所预料。

内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。