1 |
2
|
Manuela
|
// 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 |
|
|
});
|