Bootstrap scrollspy 源码解读
最近有在做一个滚动效果即:左边导航栏,右边内容,然后滚动左边导航栏导航当对应的内容块显示会激活导航。阅读了下Bootstrap的scrollspy源码,记录如下:
功能需求:
- 当点击导航栏的时候会显示对应的内容块到顶部。
- 当页面滚动的时候,当到达对应导航的内容块,则会激活导航。
需要注意的是因为当容器滚动的时候页面上面的元素有可能,比如当滚动50px,容器有元素就会浮动,从而造成滚动计算的时候会出现偏差, 就需要去重新计算offsets和targets,这个时候就需要调用refresh方法进行重新计算offsets和targets。
Bootstrap scrollspy源码解读:
|
|
首先在ScrollSpy
构造函数中,先获得滚动容器,导航选择器等,然后调用refresh
和process
函数来初始化实例。
获得容器的内容高度scrollHeight
,scrollHeight。
并绑定滚动容器的滚动事件为this.process
。
再来看refresh
函数:
|
|
这里的意思是设置实例的内容块的offsets和导航栏的targets,并按升序排列。这里有一个问题就是为什么当滚动的容器不是window的话offsetBase
为滚动容器的滚动距离?因为当一个元素在一个滚动容器里面的时候元素在滚动容器中的绝对位移值是元素的position().top
的值加上滚动容器的滚动距离,所以这里需要写上滚动容器的滚动距离。
接下来是process
函数:
|
|
|
|
当前激活的导航和targets数组一一比对如果不是当前激活导航则再比对,注意到这里的比对是从位移数组的最后一个开始倒序进行比对的, 然后当滚动距离大于位移数组的当前并且小于下一个,或者位移数组的最后一个不存在,即为最后一个导航的时候。则激活目标导航, 那么这里你所看到的现象即: 当上一个目标内容元素完全消失于viewport(视窗)之中的时候,下一个内容块到达视窗顶部的时候即激活当前的导航所在的元素。当当然这里也有性能优化的意思,然后倒序来比较有一个好处就是,如果是升序比较就得计算那个内容元素的高度来进行比较,但是倒序则不用。
疑问:
- 为什么当滚动容器是body的时候
offsetMethod
为offset非body
的时候为position?
因为当滚动容器为body的时候就得计算元素在body上面的位移,而如果非body的话就在滚动容器里面比如<div class="scroll-container"></div>
当滚动的内容在里面的时候得设置滚动容器的样式position: relative
。
当设置为position
的时候里面的内容元素即为相对于此容器的位移而不是相对于body
。
翻看jQuery源码
:
|
|
这里的源码大概意思如果元素不是fixed
定位则通过offsetParent
函数找出最近的定位的元素。
接下来是激活导航的方法:
|
|
最后是那个noConflict
方法, 因为有可能会有重名方法的插件所以需要使用这个关于这个的处理可以参见这里。我在这个基础上增加了自己的一个处理方法。
|
|
在实际使用的过程中,根据所使用的Bootstrap插件在页面中出现的位置会有不同的处理方法,下面分情况来讲解:
- 当Bootstrap插件在自定义的插件之后的时候, 若想调用自定义的插件则
$.fn.scrollspy.noConflict
即可。 若Bootstrap插件在自定义的插件之前: 则有两种解决办法:
可以在两个插件之间写上
123var Af = $.fn.scrollspy.noConflict()$.fn.Af = Af这样后面想要调用该方法就调用`$(el).Af()`或者是把后面自定义的插件写成类似这样:
12345678910(function($){var old = $.fn.scrollspy; //必须写在第一行$.fn.scrollspy=function(){alert("自定义scrollspy插件");}$.fn.scrollspy.noConflict = function () {$.fn.scrollspy = oldreturn this}})(jQuery);
疑问:
- 这里为什么要用
parents
?
|
|
清除激活状态导航的激活状态:
|
|
- 当那些导航是异步请求加载出来的,这个时候应该如何做?
2017.10.8 如果导航是异步请求出来的可以在数据请求完成后再去进行实例化。
结论
其实,这个说到底不管滚动的容器是window还是不是,基本上都是要以元素的getBoundingClientRect
属性为准,看过jQuery的源码即可知。