前端开发您现在的位置是:首页 > 博客日志 > 前端开发

bootstrap源码之滚动监听组件scrollspy.js详解

<a href='mailto:'>微wx笑</a>的头像微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(一定是父级)要一致。utP无知

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高亮。utP无知

ScrollSpy构造函数

首先新建一个构造函数,如下:utP无知

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()
  }

该构造函数主要干了啥:utP无知

1.基本设置,主要是设置当前滚动元素是设置的body还是具体的某一块元素;其次是导航的结构要是.nav li > a的结构,也就是你的菜单中也要有.nav这个class。utP无知

2.监听元素滚动的时候,执行process方法。utP无知

3.同时初始化的时候也执行了refresh与process方法。utP无知

下面讲解一下这几个方法。utP无知

getScrolHeight方法

获取滚动容器的内容高度(包含被隐藏部分)utP无知

1
this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)

refresh方法

刷新并存储滚动容器内各hashkey的值utP无知

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])
      })
 
  }

它主要实现了什么呢?utP无知

1.默认用offset来获取定位值,如果滚动区域不是window则用position来获取utP无知

1
2
3
4
if (!$.isWindow(this.$scrollElement[0])) {
      offsetMethod = 'position'
      offsetBase   = this.$scrollElement.scrollTop()
    }

2.根据导航上的hashkey来遍历获取 滚动区域内的hashkey对应的offset值:utP无知

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方法

滚动条事件触发函数,用于计算当前需要高亮那个导航菜单utP无知

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])
    }
  }

主要作用:utP无知

1.获取滚动容器已滚动距离:utP无知

1
var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset

2.滚动容器可以滚动的最大高度:utP无知

1
var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()

3.设置滚动元素逻辑,给当前匹配元素添加高亮:utP无知

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方法

设置指定的导航菜单高亮utP无知

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方法

清除所有高亮菜单utP无知

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);

 utP无知

转自:https://www.cnblogs.com/moqiutao/p/6568073.html utP无知


utP无知

注意:utP无知

如果不添加 data-offset="70" ,那么选中状态就有问题,这个值是要根据你自己的页面去调整的。utP无知

依赖 JQuery,BootstraputP无知

本文为转载文章,版权归原作者所有,不代表本站立场和观点。

很赞哦! (35) 有话说 (0)

文章评论