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="width: 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去调试。答案如我们所预料。
内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。