1+ /**
2+ * Extend jquery with a scrollspy plugin.
3+ * This watches the window scroll and fires events when elements are scrolled into viewport.
4+ *
5+ * throttle() and getTime() taken from Underscore.js
6+ * https://github.com/jashkenas/underscore
7+ *
8+ * @author Copyright 2013 John Smart
9+ * @license https://raw.github.com/thesmart/jquery-scrollspy/master/LICENSE
10+ * @see https://github.com/thesmart
11+ * @version 0.1.2
12+ */
13+ ( function ( $ ) {
14+
15+ var jWindow = $ ( window ) ;
16+ var elements = [ ] ;
17+ var elementsInView = [ ] ;
18+ var isSpying = false ;
19+ var ticks = 0 ;
20+ var offset = {
21+ top : 0 ,
22+ right : 0 ,
23+ bottom : 0 ,
24+ left : 0 ,
25+ }
26+
27+ /**
28+ * Find elements that are within the boundary
29+ * @param {number } top
30+ * @param {number } right
31+ * @param {number } bottom
32+ * @param {number } left
33+ * @return {jQuery } A collection of elements
34+ */
35+ function findElements ( top , right , bottom , left ) {
36+ var hits = $ ( ) ;
37+ $ . each ( elements , function ( i , element ) {
38+ var elTop = element . offset ( ) . top ,
39+ elLeft = element . offset ( ) . left ,
40+ elRight = elLeft + element . width ( ) ,
41+ elBottom = elTop + element . height ( ) ;
42+
43+ var isIntersect = ! ( elLeft > right ||
44+ elRight < left ||
45+ elTop > bottom ||
46+ elBottom < top ) ;
47+
48+ if ( isIntersect ) {
49+ hits . push ( element ) ;
50+ }
51+ } ) ;
52+
53+ return hits ;
54+ }
55+
56+ /**
57+ * Called when the user scrolls the window
58+ */
59+ function onScroll ( ) {
60+ // unique tick id
61+ ++ ticks ;
62+
63+ // viewport rectangle
64+ var top = jWindow . scrollTop ( ) ,
65+ left = jWindow . scrollLeft ( ) ,
66+ right = left + jWindow . width ( ) ,
67+ bottom = top + jWindow . height ( ) ;
68+
69+ // determine which elements are in view
70+ var intersections = findElements ( top + offset . top , right + offset . right , bottom + offset . bottom , left + offset . left ) ;
71+ $ . each ( intersections , function ( i , element ) {
72+ var lastTick = element . data ( 'scrollSpy:ticks' ) ;
73+ if ( typeof lastTick != 'number' ) {
74+ // entered into view
75+ element . triggerHandler ( 'scrollSpy:enter' ) ;
76+ }
77+
78+ // update tick id
79+ element . data ( 'scrollSpy:ticks' , ticks ) ;
80+ } ) ;
81+
82+ // determine which elements are no longer in view
83+ $ . each ( elementsInView , function ( i , element ) {
84+ var lastTick = element . data ( 'scrollSpy:ticks' ) ;
85+ if ( typeof lastTick == 'number' && lastTick !== ticks ) {
86+ // exited from view
87+ element . triggerHandler ( 'scrollSpy:exit' ) ;
88+ element . data ( 'scrollSpy:ticks' , null ) ;
89+ }
90+ } ) ;
91+
92+ // remember elements in view for next tick
93+ elementsInView = intersections ;
94+ }
95+
96+ /**
97+ * Called when window is resized
98+ */
99+ function onWinSize ( ) {
100+ jWindow . trigger ( 'scrollSpy:winSize' ) ;
101+ }
102+
103+ /**
104+ * Get time in ms
105+ * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
106+ * @type {function }
107+ * @return {number }
108+ */
109+ var getTime = ( Date . now || function ( ) {
110+ return new Date ( ) . getTime ( ) ;
111+ } ) ;
112+
113+ /**
114+ * Returns a function, that, when invoked, will only be triggered at most once
115+ * during a given window of time. Normally, the throttled function will run
116+ * as much as it can, without ever going more than once per `wait` duration;
117+ * but if you'd like to disable the execution on the leading edge, pass
118+ * `{leading: false}`. To disable execution on the trailing edge, ditto.
119+ * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
120+ * @param {function } func
121+ * @param {number } wait
122+ * @param {Object= } options
123+ * @returns {Function }
124+ */
125+ function throttle ( func , wait , options ) {
126+ var context , args , result ;
127+ var timeout = null ;
128+ var previous = 0 ;
129+ options || ( options = { } ) ;
130+ var later = function ( ) {
131+ previous = options . leading === false ? 0 : getTime ( ) ;
132+ timeout = null ;
133+ result = func . apply ( context , args ) ;
134+ context = args = null ;
135+ } ;
136+ return function ( ) {
137+ var now = getTime ( ) ;
138+ if ( ! previous && options . leading === false ) previous = now ;
139+ var remaining = wait - ( now - previous ) ;
140+ context = this ;
141+ args = arguments ;
142+ if ( remaining <= 0 ) {
143+ clearTimeout ( timeout ) ;
144+ timeout = null ;
145+ previous = now ;
146+ result = func . apply ( context , args ) ;
147+ context = args = null ;
148+ } else if ( ! timeout && options . trailing !== false ) {
149+ timeout = setTimeout ( later , remaining ) ;
150+ }
151+ return result ;
152+ } ;
153+ } ;
154+
155+ /**
156+ * Enables ScrollSpy using a selector
157+ * @param {jQuery|string } selector The elements collection, or a selector
158+ * @param {Object= } options Optional.
159+ throttle : number -> scrollspy throttling. Default: 100 ms
160+ offsetTop : number -> offset from top. Default: 0
161+ offsetRight : number -> offset from right. Default: 0
162+ offsetBottom : number -> offset from bottom. Default: 0
163+ offsetLeft : number -> offset from left. Default: 0
164+ * @returns {jQuery }
165+ */
166+ $ . scrollSpy = function ( selector , options ) {
167+ selector = $ ( selector ) ;
168+ selector . each ( function ( i , element ) {
169+ elements . push ( $ ( element ) ) ;
170+ } ) ;
171+ options = options || {
172+ throttle : 100
173+ } ;
174+
175+ offset . top = options . offsetTop || 0 ;
176+ offset . right = options . offsetRight || 0 ;
177+ offset . bottom = options . offsetBottom || 0 ;
178+ offset . left = options . offsetLeft || 0 ;
179+
180+ var throttledScroll = throttle ( onScroll , options . throttle || 100 ) ;
181+ var readyScroll = function ( ) {
182+ $ ( document ) . ready ( throttledScroll ) ;
183+ } ;
184+
185+ if ( ! isSpying ) {
186+ jWindow . on ( 'scroll' , readyScroll ) ;
187+ jWindow . on ( 'resize' , readyScroll ) ;
188+ isSpying = true ;
189+ }
190+
191+ // perform a scan once, after current execution context, and after dom is ready
192+ setTimeout ( readyScroll , 0 ) ;
193+
194+ return selector ;
195+ } ;
196+
197+ /**
198+ * Listen for window resize events
199+ * @param {Object= } options Optional. Set { throttle: number } to change throttling. Default: 100 ms
200+ * @returns {jQuery } $(window)
201+ */
202+ $ . winSizeSpy = function ( options ) {
203+ $ . winSizeSpy = function ( ) { return jWindow ; } ; // lock from multiple calls
204+ options = options || {
205+ throttle : 100
206+ } ;
207+ return jWindow . on ( 'resize' , throttle ( onWinSize , options . throttle || 100 ) ) ;
208+ } ;
209+
210+ /**
211+ * Enables ScrollSpy on a collection of elements
212+ * e.g. $('.scrollSpy').scrollSpy()
213+ * @param {Object= } options Optional.
214+ throttle : number -> scrollspy throttling. Default: 100 ms
215+ offsetTop : number -> offset from top. Default: 0
216+ offsetRight : number -> offset from right. Default: 0
217+ offsetBottom : number -> offset from bottom. Default: 0
218+ offsetLeft : number -> offset from left. Default: 0
219+ * @returns {jQuery }
220+ */
221+ $ . fn . scrollSpy = function ( options ) {
222+ return $ . scrollSpy ( $ ( this ) , options ) ;
223+ } ;
224+
225+ } ) ( jQuery ) ;
0 commit comments