bootstrap源码之滚动监听组件scrollspy.js详解
微wx笑
2021-08-07【前端开发】
145
6
0关键字:
bootstrap 源码 滚动监听 组件 scrollspy
javascript文章页面,左侧滚动内容时右侧目录也在跟着变化,怎么实现的?
其实滚动监听使用的情况还是很多的,比如导航居于右侧,当主题内容滚动某一块的时候,右侧导航对应的要高亮。
目录
实现功能
1、当滚动区域内设置的hashkey距离顶点到有效位置时,就关联设置其导航上的指定项
2、导航必须是 .nav > li > a 结构,并且a上href或data-target要绑定hashkey
3、菜单上必须有.nav样式
4、滚动区域的data-target与导航父级Id(一定是父级)要一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <!DOCTYPE html> < html > < head > < title >Example</ title > < script type = "text/javascript" src = "jquery-1.7.2.min.js" ></ script > < script src = "scrollspy.js" ></ script > < link href = "bootstrap.min.css" rel = "stylesheet" > </ head > < body > < div id = "selector" class = "navbar navbar-default" > < ul class = "nav navbar-nav" > < li >< a href = "#one" >one</ a > </ li > < li >< a href = "#two" >two</ a > </ li > < li >< a href = "#three" >three</ a > </ li > </ ul > </ div > < div data-spy = "scroll" data-target = "#selector" data-offset = "70" style = "height:500px; overflow:hidden;overflow-y: auto;" > < h4 id = "one" >ibe</ h4 > < p >One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br /></ p > < p >One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br /></ p > < p >One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br /></ p > < p >One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br /></ p > < p >One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br />One的具体内容< br /></ p > < h4 id = "two" >two</ h4 > < p >two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br /></ p > < p >two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br /></ p > < p >two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br /></ p > < p >two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br /></ p > < p >two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br />two的具体内容< br /></ p > < h4 id = "three" >three</ h4 > < p >three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br /></ p > < p >three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br /></ p > < p >three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br /></ p > < p >three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br /></ p > < p >three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br />three的具体内容< br /></ p > </ div > </ body > </ html > |
下面来看一下实现的具体代码,原理:当滚动容器内的hashkey位置距离容器顶部只有 offset设置的值,就会设置导航中对应的href高亮。
ScrollSpy构造函数
首先新建一个构造函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function ScrollSpy(element, options) { this .$body = $(document.body) this .$scrollElement = $(element).is(document.body) ? $(window) : $(element) this .options = $.extend({}, ScrollSpy.DEFAULTS, options) this .selector = ( this .options.target || '' ) + ' .nav li > a' this .offsets = [] this .targets = [] this .activeTarget = null this .scrollHeight = 0 this .$scrollElement.on( 'scroll.bs.scrollspy' , $.proxy( this .process, this )) this .refresh() this .process() } |
该构造函数主要干了啥:
1.基本设置,主要是设置当前滚动元素是设置的body还是具体的某一块元素;其次是导航的结构要是.nav li > a的结构,也就是你的菜单中也要有.nav这个class。
2.监听元素滚动的时候,执行process方法。
3.同时初始化的时候也执行了refresh与process方法。
下面讲解一下这几个方法。
getScrolHeight方法
获取滚动容器的内容高度(包含被隐藏部分)
1 | this .$scrollElement[0].scrollHeight || Math.max( this .$body[0].scrollHeight, document.documentElement.scrollHeight) |
refresh方法
刷新并存储滚动容器内各hashkey的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ScrollSpy.prototype.refresh = function () { var that = this var offsetMethod = 'offset' var offsetBase = 0 this .offsets = [] this .targets = [] this .scrollHeight = this .getScrollHeight() if (!$.isWindow( this .$scrollElement[0])) { offsetMethod = 'position' offsetBase = this .$scrollElement.scrollTop() } this .$body .find( this .selector) .map( function () { var $el = $( this ) var href = $el.data( 'target' ) || $el.attr( 'href' ) var $href = /^ #./.test(href) && $(href) return ($href && $href.length && $href.is( ':visible' ) && [[$href[offsetMethod]().top + offsetBase, href]]) || null }) .sort( function (a, b) { return a[0] - b[0] }) .each( function () { that.offsets.push( this [0]) that.targets.push( this [1]) }) } |
它主要实现了什么呢?
1.默认用offset来获取定位值,如果滚动区域不是window则用position来获取
1 2 3 4 | if (!$.isWindow( this .$scrollElement[0])) { offsetMethod = 'position' offsetBase = this .$scrollElement.scrollTop() } |
2.根据导航上的hashkey来遍历获取 滚动区域内的hashkey对应的offset值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | this .$body .find( this .selector) .map( function () { var $el = $( this ) var href = $el.data( 'target' ) || $el.attr( 'href' ) var $href = /^ #./.test(href) && $(href) return ($href && $href.length && $href.is( ':visible' ) && [[$href[offsetMethod]().top + offsetBase, href]]) || null }) .sort( function (a, b) { return a[0] - b[0] }) .each( function () { that.offsets.push( this [0]) that.targets.push( this [1]) }) |
process方法
滚动条事件触发函数,用于计算当前需要高亮那个导航菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ScrollSpy.prototype.process = function () { var scrollTop = this .$scrollElement.scrollTop() + this .options.offset var scrollHeight = this .getScrollHeight() var maxScroll = this .options.offset + scrollHeight - this .$scrollElement.height() var offsets = this .offsets var targets = this .targets var activeTarget = this .activeTarget var i if ( this .scrollHeight != scrollHeight) { this .refresh() } if (scrollTop >= maxScroll) { return activeTarget != (i = targets[targets.length - 1]) && this .activate(i) } if (activeTarget && scrollTop < offsets[0]) { this .activeTarget = null return this .clear() } for (i = offsets.length; i--;) { activeTarget != targets[i] && scrollTop >= offsets[i] && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && this .activate(targets[i]) } } |
主要作用:
1.获取滚动容器已滚动距离:
1 | var scrollTop = this .$scrollElement.scrollTop() + this .options.offset |
2.滚动容器可以滚动的最大高度:
1 | var maxScroll = this .options.offset + scrollHeight - this .$scrollElement.height() |
3.设置滚动元素逻辑,给当前匹配元素添加高亮:
1 2 3 4 5 6 | for (i = offsets.length; i--;) { activeTarget != targets[i] && scrollTop >= offsets[i] && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && this .activate(targets[i]) } |
active方法
设置指定的导航菜单高亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ScrollSpy.prototype.activate = function (target) { this .activeTarget = target this .clear() var selector = this .selector + '[data-target="' + target + '"],' + this .selector + '[href="' + target + '"]' var active = $(selector) .parents( 'li' ) .addClass( 'active' ) if (active.parent( '.dropdown-menu' ).length) { active = active .closest( 'li.dropdown' ) .addClass( 'active' ) } active.trigger( 'activate.bs.scrollspy' ) } |
clear方法
清除所有高亮菜单
1 2 3 4 5 | ScrollSpy.prototype.clear = function () { $( this .selector) .parentsUntil( this .options.target, '.active' ) .removeClass( 'active' ) } |
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | + function ($) { 'use strict' ; // SCROLLSPY CLASS DEFINITION // ========================== function ScrollSpy(element, options) { this .$body = $(document.body) this .$scrollElement = $(element).is(document.body) ? $(window) : $(element) this .options = $.extend({}, ScrollSpy.DEFAULTS, options) this .selector = ( this .options.target || '' ) + ' .nav li > a' this .offsets = [] this .targets = [] this .activeTarget = null this .scrollHeight = 0 this .$scrollElement.on( 'scroll.bs.scrollspy' , $.proxy( this .process, this )) this .refresh() this .process() } ScrollSpy.VERSION = '3.3.7' ScrollSpy.DEFAULTS = { offset: 10 } ScrollSpy.prototype.getScrollHeight = function () { return this .$scrollElement[0].scrollHeight || Math.max( this .$body[0].scrollHeight, document.documentElement.scrollHeight) } ScrollSpy.prototype.refresh = function () { var that = this var offsetMethod = 'offset' var offsetBase = 0 this .offsets = [] this .targets = [] this .scrollHeight = this .getScrollHeight() if (!$.isWindow( this .$scrollElement[0])) { offsetMethod = 'position' offsetBase = this .$scrollElement.scrollTop() } this .$body .find( this .selector) .map( function () { var $el = $( this ) var href = $el.data( 'target' ) || $el.attr( 'href' ) var $href = /^ #./.test(href) && $(href) return ($href && $href.length && $href.is( ':visible' ) && [[$href[offsetMethod]().top + offsetBase, href]]) || null }) .sort( function (a, b) { return a[0] - b[0] }) .each( function () { that.offsets.push( this [0]) that.targets.push( this [1]) }) } ScrollSpy.prototype.process = function () { var scrollTop = this .$scrollElement.scrollTop() + this .options.offset var scrollHeight = this .getScrollHeight() var maxScroll = this .options.offset + scrollHeight - this .$scrollElement.height() var offsets = this .offsets var targets = this .targets var activeTarget = this .activeTarget var i if ( this .scrollHeight != scrollHeight) { this .refresh() } if (scrollTop >= maxScroll) { return activeTarget != (i = targets[targets.length - 1]) && this .activate(i) } if (activeTarget && scrollTop < offsets[0]) { this .activeTarget = null return this .clear() } for (i = offsets.length; i--;) { activeTarget != targets[i] && scrollTop >= offsets[i] && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && this .activate(targets[i]) } } ScrollSpy.prototype.activate = function (target) { this .activeTarget = target this .clear() var selector = this .selector + '[data-target="' + target + '"],' + this .selector + '[href="' + target + '"]' var active = $(selector) .parents( 'li' ) .addClass( 'active' ) if (active.parent( '.dropdown-menu' ).length) { active = active .closest( 'li.dropdown' ) .addClass( 'active' ) } active.trigger( 'activate.bs.scrollspy' ) } ScrollSpy.prototype.clear = function () { $( this .selector) .parentsUntil( this .options.target, '.active' ) .removeClass( 'active' ) } // SCROLLSPY PLUGIN DEFINITION // =========================== function Plugin(option) { return this .each( function () { var $ this = $( this ) var data = $ this .data( 'bs.scrollspy' ) var options = typeof option == 'object' && option if (!data) $ this .data( 'bs.scrollspy' , (data = new ScrollSpy( this , options))) if ( typeof option == 'string' ) data[option]() }) } var old = $.fn.scrollspy $.fn.scrollspy = Plugin $.fn.scrollspy.Constructor = ScrollSpy // SCROLLSPY NO CONFLICT // ===================== $.fn.scrollspy.noConflict = function () { $.fn.scrollspy = old return this } // SCROLLSPY DATA-API // ================== $(window).on( 'load.bs.scrollspy.data-api' , function () { $( '[data-spy="scroll"]' ).each( function () { var $spy = $( this ) Plugin.call($spy, $spy.data()) }) }) }(jQuery); |
转自:https://www.cnblogs.com/moqiutao/p/6568073.html
注意:
如果不添加 data-offset="70" ,那么选中状态就有问题,这个值是要根据你自己的页面去调整的。
依赖 JQuery,Bootstrap
本文为转载文章,版权归原作者所有,不代表本站立场和观点。