1
|
// Native Javascript for Bootstrap 3 | ScrollSpy
|
2
|
// by dnp_theme
|
3
|
|
4
|
(function(factory){
|
5
|
|
6
|
// CommonJS/RequireJS and "native" compatibility
|
7
|
if(typeof module !== "undefined" && typeof exports == "object") {
|
8
|
// A commonJS/RequireJS environment
|
9
|
if(typeof window != "undefined") {
|
10
|
// Window and document exist, so return the factory's return value.
|
11
|
module.exports = factory();
|
12
|
} else {
|
13
|
// Let the user give the factory a Window and Document.
|
14
|
module.exports = factory;
|
15
|
}
|
16
|
} else {
|
17
|
// Assume a traditional browser.
|
18
|
window.ScrollSpy = factory();
|
19
|
}
|
20
|
|
21
|
})(function(){
|
22
|
|
23
|
//SCROLLSPY DEFINITION
|
24
|
var ScrollSpy = function(element,item,options) {
|
25
|
options = options || {};
|
26
|
|
27
|
//this is the container element we spy it's elements on
|
28
|
this.element = typeof element === 'object' ? element : document.querySelector(element);
|
29
|
|
30
|
this.options = {};
|
31
|
this.isIE = (new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null) ? parseFloat( RegExp.$1 ) : false;
|
32
|
// this is the UL menu component our scrollSpy object will target, configure and required by the container element
|
33
|
this.options.target = options.target ? (typeof options.target === 'object' ? options.target : document.querySelector(options.target)) : null;
|
34
|
|
35
|
//we need to determine the index of each menu item
|
36
|
this.items = this.options.target && this.options.target.getElementsByTagName('A');
|
37
|
|
38
|
this.item = item;
|
39
|
// the parent LI element
|
40
|
this.parent = this.item.parentNode;
|
41
|
|
42
|
// the upper level LI ^ UL ^ LI, this is required for dropdown menus
|
43
|
this.parentParent = this.parent.parentNode.parentNode;
|
44
|
|
45
|
this.tg = this.item.href && document.getElementById(this.item.getAttribute('href').replace('#',''));
|
46
|
this.active = false;
|
47
|
this.topEdge = 0;
|
48
|
this.bottomEdge = 0;
|
49
|
|
50
|
//determine which is the real scrollTarget
|
51
|
if ( this.element.offsetHeight < this.element.scrollHeight ) { // or this.scrollHeight()
|
52
|
this.scrollTarget = this.element;
|
53
|
} else {
|
54
|
this.scrollTarget = window;
|
55
|
}
|
56
|
|
57
|
if ( this.options.target ) {
|
58
|
this.init();
|
59
|
}
|
60
|
};
|
61
|
|
62
|
//SCROLLSPY METHODS
|
63
|
ScrollSpy.prototype = {
|
64
|
init: function () {
|
65
|
if ( this.item.getAttribute('href') && this.item.getAttribute('href').indexOf('#') > -1 ) {
|
66
|
//actions
|
67
|
this.checkEdges();
|
68
|
this.refresh()
|
69
|
this.scrollEvent();
|
70
|
if (!(this.isIE && this.isIE < 9)) { this.resizeEvent(); }
|
71
|
}
|
72
|
},
|
73
|
topLimit: function () { // the target offset
|
74
|
if ( this.scrollTarget === window ) {
|
75
|
return this.tg.getBoundingClientRect().top + this.scrollOffset() - 5
|
76
|
} else {
|
77
|
return this.tg.offsetTop;
|
78
|
}
|
79
|
|
80
|
},
|
81
|
bottomLimit: function () {
|
82
|
return this.topLimit() + this.tg.clientHeight
|
83
|
},
|
84
|
checkEdges: function () {
|
85
|
this.topEdge = this.topLimit();
|
86
|
this.bottomEdge = this.bottomLimit()
|
87
|
},
|
88
|
scrollOffset: function () {
|
89
|
if ( this.scrollTarget === window ) {
|
90
|
return window.pageYOffset || document.documentElement.scrollTop
|
91
|
} else {
|
92
|
return this.element.scrollTop
|
93
|
}
|
94
|
},
|
95
|
activate: function () {
|
96
|
if ( this.parent && this.parent.tagName === 'LI' && !/active/.test(this.parent.className) ) {
|
97
|
this.addClass(this.parent,'active');
|
98
|
if ( this.parentParent && this.parentParent.tagName === 'LI' // activate the dropdown as well
|
99
|
&& /dropdown/.test(this.parentParent.className)
|
100
|
&& !/active/.test(this.parentParent.className) ) { this.addClass(this.parentParent,'active'); }
|
101
|
this.active = true
|
102
|
}
|
103
|
},
|
104
|
deactivate: function () {
|
105
|
if ( this.parent && this.parent.tagName === 'LI' && /active/.test(this.parent.className) ) {
|
106
|
this.removeClass(this.parent,'active');
|
107
|
if ( this.parentParent && this.parentParent.tagName === 'LI' // deactivate the dropdown as well
|
108
|
&& /dropdown/.test(this.parentParent.className)
|
109
|
&& /active/.test(this.parentParent.className) ) { this.removeClass(this.parentParent,'active'); }
|
110
|
this.active = false
|
111
|
}
|
112
|
},
|
113
|
toggle: function () {
|
114
|
if ( this.active === false
|
115
|
&& ( this.bottomEdge > this.scrollOffset() && this.scrollOffset() >= this.topEdge )) { //regular use, scroll just entered the element's topLimit or bottomLimit
|
116
|
this.activate();
|
117
|
} else if (this.active === true && (this.bottomEdge <= this.scrollOffset() && this.scrollOffset() < this.topEdge )) {
|
118
|
this.deactivate()
|
119
|
}
|
120
|
},
|
121
|
refresh : function () { // check edges again
|
122
|
this.deactivate();
|
123
|
this.checkEdges();
|
124
|
|
125
|
this.toggle() // If any case update values again
|
126
|
},
|
127
|
scrollEvent : function(){
|
128
|
var self = this;
|
129
|
this.scrollTarget.addEventListener('scroll', onSpyScroll, false);
|
130
|
function onSpyScroll() {
|
131
|
self.refresh();
|
132
|
}
|
133
|
},
|
134
|
resizeEvent : function(){
|
135
|
var self = this;
|
136
|
window.addEventListener('resize', onSpyResize, false);
|
137
|
function onSpyResize() {
|
138
|
self.refresh()
|
139
|
}
|
140
|
},
|
141
|
scrollHeight : function() {
|
142
|
if ( this.scrollTarget === window ) {
|
143
|
return Math.max( document.body.scrollHeight, document.body.offsetHeight,
|
144
|
document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );
|
145
|
} else {
|
146
|
return this.element.scrollHeight
|
147
|
}
|
148
|
},
|
149
|
addClass : function(el,c) {
|
150
|
if (el.classList) { el.classList.add(c); } else { el.className += ' '+c; }
|
151
|
},
|
152
|
removeClass : function(el,c) {
|
153
|
if (el.classList) { el.classList.remove(c); } else { el.className = el.className.replace(c,'').replace(/^\s+|\s+$/g,''); }
|
154
|
}
|
155
|
};
|
156
|
|
157
|
|
158
|
//SCROLLSPY API
|
159
|
//=============
|
160
|
var scrollSpyes = document.querySelectorAll('[data-spy="scroll"]'), i = 0, ssl = scrollSpyes.length; // mostly is the document.body or a large container with many elements having id="not-null-id"
|
161
|
for (i;i<ssl;i++) {
|
162
|
var spy = scrollSpyes[i], options = {};
|
163
|
options.target = spy.getAttribute('data-target') || null; // this must be a .nav component with id="not-null"
|
164
|
if ( options.target !== null ) {
|
165
|
var menu = options.target === 'object' ? options.target : document.querySelector(options.target),
|
166
|
items = menu.querySelectorAll('a'), j = 0, il = items.length;
|
167
|
for (j;j<il;j++) {
|
168
|
var item = items[j];
|
169
|
if ( item.href && item.getAttribute('href') !== '#' )
|
170
|
new ScrollSpy(spy, item, options);
|
171
|
}
|
172
|
}
|
173
|
}
|
174
|
|
175
|
return ScrollSpy;
|
176
|
|
177
|
});
|