3
3
var Shira ;
4
4
( function ( Shira , $ ) {
5
5
( function ( ScrollWatch ) {
6
+ /**
7
+ * Iterate an array (helper)
8
+ *
9
+ * @param {Object } thisArg
10
+ * @param {Array } arr
11
+ * @param {Function } callback
12
+ */
13
+ function foreach ( thisArg , arr , callback ) {
14
+ for ( var i = 0 ; i < arr . length ; ++ i ) {
15
+ if ( callback . call ( thisArg , i , arr [ i ] ) === false ) {
16
+ break ;
17
+ }
18
+ }
19
+ }
20
+
6
21
/**
7
22
* @constructor
8
23
*
@@ -53,6 +68,7 @@ var Shira;
53
68
viewMarginBottom : 0 ,
54
69
stickyOffsetTop : 5 ,
55
70
stickyOffsetBottom : 5 ,
71
+ clamp : false ,
56
72
focusRatio : 0.38196601125010515 ,
57
73
focusOffset : 0 ,
58
74
debugFocusLine : false
@@ -99,7 +115,7 @@ var Shira;
99
115
while ( ! scrollable ) {
100
116
elem = elem . offsetParent ;
101
117
102
- if ( elem && 1 === elem . nodeType && 'BODY' !== elem . tagName && 'HTML' !== elem . tagName ) {
118
+ if ( elem && elem . nodeType === 1 && 'BODY' !== elem . tagName && 'HTML' !== elem . tagName ) {
103
119
var overflowY = $ ( elem ) . css ( 'overflow-y' ) ;
104
120
scrollable = 'auto' === overflowY || 'scroll' === overflowY ;
105
121
} else {
@@ -140,23 +156,32 @@ var Shira;
140
156
updateSectionBoundaries : function ( ) {
141
157
this . sectionBoundaries = [ ] ;
142
158
143
- for ( var i = 0 ; i < this . sections . length ; ++ i ) {
144
- var top = this . getElementY ( this . sections [ i ] , this . scroller ) ;
145
- var bottom = top + this . sections [ i ] . offsetHeight ;
146
- this . sectionBoundaries . push ( [ top , bottom ] ) ;
147
- }
159
+ foreach ( this , this . sections , function ( i , section ) {
160
+ var top = this . getElementY ( section , this . scroller ) ;
161
+ var bottom = top + section . offsetHeight ;
162
+ this . sectionBoundaries . push ( { index : i , top : top , bottom : bottom } ) ;
163
+ } ) ;
164
+
148
165
this . sectionBoundaries . sort ( this . sortSectionBoundaries ) ;
166
+
167
+ if ( this . options . clamp ) {
168
+ foreach ( this , this . sectionBoundaries , function ( i , boundary ) {
169
+ if ( i < this . sectionBoundaries . length - 1 ) {
170
+ boundary . bottom = this . sectionBoundaries [ i + 1 ] . top - 1 ;
171
+ }
172
+ } ) ;
173
+ }
149
174
} ,
150
175
151
176
/**
152
177
* Sort calculated section boundaries
153
178
*
154
- * @param {Array } a
155
- * @param {Array } b
179
+ * @param {Object } a
180
+ * @param {Object } b
156
181
* @returns {Number }
157
182
*/
158
183
sortSectionBoundaries : function ( a , b ) {
159
- return a [ 0 ] - b [ 0 ] ;
184
+ return a . top - b . top ;
160
185
} ,
161
186
162
187
/**
@@ -181,10 +206,10 @@ var Shira;
181
206
var top = $ ( this . scroller ) . scrollTop ( ) ;
182
207
var bottom = top + this . scrollerVisibleHeight ;
183
208
184
- if ( 0 !== this . options . viewMarginTop ) {
209
+ if ( this . options . viewMarginTop !== 0 ) {
185
210
top += this . options . viewMarginTop ;
186
211
}
187
- if ( 0 !== this . options . viewMarginBottom ) {
212
+ if ( this . options . viewMarginBottom !== 0 ) {
188
213
bottom = Math . max ( top + 1 , bottom - this . options . viewMarginBottom ) ;
189
214
}
190
215
@@ -201,63 +226,68 @@ var Shira;
201
226
* @returns {Array }
202
227
*/
203
228
determineFocusCandidates : function ( view ) {
204
- var focusCandidates = [ ] , forcedIndex = null ;
229
+ var that = this , focusCandidates = [ ] , forcedBoundary = null ;
205
230
206
- // see if a certain section must be forced
231
+ // see if a certain boundary must be forced
207
232
if ( this . scrollerFullHeight - view . bottom < this . options . stickyOffsetBottom ) {
208
- // always choose last section if the view is near the end
209
- forcedIndex = this . sectionBoundaries . length - 1 ;
233
+ // always choose last boundary if the view is near the end
234
+ forcedBoundary = this . sectionBoundaries [ this . sectionBoundaries . length - 1 ] ;
210
235
} else if ( view . top - this . options . viewMarginTop < this . options . stickyOffsetTop ) {
211
- // always choose first section if the view is near the beginning
212
- forcedIndex = 0 ;
236
+ // always choose first boundary if the view is near the beginning
237
+ forcedBoundary = this . sectionBoundaries [ 0 ] ;
213
238
}
214
239
215
240
// determine candidates
216
- if ( null !== forcedIndex ) {
241
+ if ( forcedBoundary !== null ) {
217
242
// forced
218
243
focusCandidates . push ( {
219
- index : forcedIndex ,
244
+ index : forcedBoundary . index ,
220
245
intersection : this . getIntersection (
221
246
view . top ,
222
247
view . bottom ,
223
- this . sectionBoundaries [ forcedIndex ] [ 0 ] ,
224
- this . sectionBoundaries [ forcedIndex ] [ 1 ]
248
+ forcedBoundary . top ,
249
+ forcedBoundary . bottom
225
250
) ,
226
- section : this . sections [ forcedIndex ]
251
+ section : this . sections [ forcedBoundary . index ]
227
252
} ) ;
228
253
} else {
229
254
// find intersecting sections
230
- for ( var i = 0 ; i < this . sectionBoundaries . length ; ++ i ) {
231
- var intersection = this . getIntersection (
232
- view . top ,
233
- view . bottom ,
234
- this . sectionBoundaries [ i ] [ 0 ] ,
235
- this . sectionBoundaries [ i ] [ 1 ]
236
- ) ;
255
+ foreach ( this , this . sectionBoundaries , function ( i , boundary ) {
256
+ var intersection = that . getIntersection ( view . top , view . bottom , boundary . top , boundary . bottom ) ;
237
257
238
- if ( null !== intersection ) {
258
+ if ( intersection !== null ) {
239
259
focusCandidates . push ( {
240
- index : i ,
260
+ index : boundary . index ,
241
261
intersection : intersection ,
242
- section : this . sections [ i ]
262
+ section : that . sections [ boundary . index ]
243
263
} ) ;
244
264
}
245
- }
265
+ } ) ;
246
266
247
- // use section closest to the top of the view if no intersection was found
248
- if ( 0 === focusCandidates . length ) {
249
- var sectionClosest = null , sectionOffsetTop ;
250
- for ( i = 0 ; i < this . sectionBoundaries . length ; ++ i ) {
251
- sectionOffsetTop = Math . abs ( this . sectionBoundaries [ i ] [ 0 ] - view . top ) ;
252
- if ( null === sectionClosest || sectionClosest [ 1 ] > sectionOffsetTop ) {
253
- sectionClosest = i ;
267
+ // find the closest boundary above if there are no intersections
268
+ if ( focusCandidates . length === 0 ) {
269
+ var closestBoundary = null ;
270
+
271
+ foreach ( this , this . sectionBoundaries , function ( _ , boundary ) {
272
+ if (
273
+ boundary . bottom < view . top
274
+ && (
275
+ closestBoundary === null
276
+ || boundary . bottom > closestBoundary . bottom
277
+ )
278
+ ) {
279
+ closestBoundary = boundary ;
254
280
}
281
+ } ) ;
282
+
283
+ if ( closestBoundary === null ) {
284
+ closestBoundary = this . sectionBoundaries [ 0 ] ;
255
285
}
256
286
257
287
focusCandidates . push ( {
258
- index : sectionClosest ,
288
+ index : closestBoundary . index ,
259
289
intersection : null ,
260
- section : this . sections [ sectionClosest ]
290
+ section : this . sections [ closestBoundary . index ]
261
291
} ) ;
262
292
}
263
293
}
@@ -276,7 +306,7 @@ var Shira;
276
306
var that = this ;
277
307
var chosenCandidate = null ;
278
308
279
- if ( 1 === focusCandidates . length ) {
309
+ if ( focusCandidates . length === 1 ) {
280
310
// single candidate available
281
311
chosenCandidate = focusCandidates [ 0 ] ;
282
312
} else {
@@ -296,37 +326,36 @@ var Shira;
296
326
297
327
// choose using intersection or distance from the focus line
298
328
case 'focus-line' :
299
- var i ;
300
329
var viewFocusLineOffset = view . top + ( view . bottom - view . top ) * this . options . focusRatio + this . options . focusOffset ;
301
330
302
331
if ( this . options . debugFocusLine ) {
303
332
this . updateDebugFocusLine ( Math . round ( viewFocusLineOffset ) ) ;
304
333
}
305
334
306
335
// find direct intersection with the focus line
307
- for ( i = 0 ; i < focusCandidates . length ; ++ i ) {
308
- if ( focusCandidates [ i ] . intersection [ 0 ] <= viewFocusLineOffset && focusCandidates [ i ] . intersection [ 1 ] >= viewFocusLineOffset ) {
309
- chosenCandidate = focusCandidates [ i ] ;
310
- break ;
336
+ foreach ( this , focusCandidates , function ( _ , candidate ) {
337
+ if ( candidate . intersection [ 0 ] <= viewFocusLineOffset && candidate . intersection [ 1 ] >= viewFocusLineOffset ) {
338
+ chosenCandidate = candidate ;
339
+ return false ;
311
340
}
312
- }
341
+ } ) ;
313
342
314
343
// find nearest candidate if no direct intersection exists
315
- if ( null === chosenCandidate ) {
316
- for ( i = 0 ; i < focusCandidates . length ; ++ i ) {
317
- focusCandidates [ i ] . focusRatioOffsetDistance = Math . min (
318
- Math . abs ( focusCandidates [ i ] . intersection [ 0 ] - viewFocusLineOffset ) ,
319
- Math . abs ( focusCandidates [ i ] . intersection [ 1 ] - viewFocusLineOffset )
344
+ if ( chosenCandidate === null ) {
345
+ foreach ( this , focusCandidates , function ( _ , candidate ) {
346
+ candidate . focusRatioOffsetDistance = Math . min (
347
+ Math . abs ( candidate . intersection [ 0 ] - viewFocusLineOffset ) ,
348
+ Math . abs ( candidate . intersection [ 1 ] - viewFocusLineOffset )
320
349
) ;
321
- }
350
+ } ) ;
322
351
focusCandidates . sort ( this . sortFocusCandidatesByDistanceToFocusRatioOffset ) ;
323
352
chosenCandidate = focusCandidates [ 0 ] ;
324
353
}
325
354
break ;
326
355
327
356
// use custom resolver
328
357
case 'custom' :
329
- if ( null === this . options . resolver ) {
358
+ if ( this . options . resolver === null ) {
330
359
throw new Error ( 'No resolver has been set' ) ;
331
360
}
332
361
chosenCandidate = this . options . resolver ( focusCandidates , view , this ) ;
@@ -352,14 +381,14 @@ var Shira;
352
381
updateDebugFocusLine : function ( focusLineOffset ) {
353
382
var that = this ;
354
383
355
- if ( null === this . debugFocusLine ) {
384
+ if ( this . debugFocusLine === null ) {
356
385
this . debugFocusLine = $ ( '<div class="scrollwatch-debug-focus-line" style="position:absolute;left:0;top:0;width:100%;border-bottom:1px solid white;outline:1px solid black;z-index:10000;box-shadow: 0 0 5px black;"></div>' )
357
386
. appendTo ( window === this . scroller ? document . body : this . scroller ) ;
358
387
}
359
388
360
389
this . debugFocusLine . css ( 'top' , focusLineOffset + 'px' ) ;
361
390
362
- if ( null !== this . debugFocusLineTimeout ) {
391
+ if ( this . debugFocusLineTimeout !== null ) {
363
392
clearTimeout ( this . debugFocusLineTimeout ) ;
364
393
}
365
394
@@ -474,17 +503,16 @@ var Shira;
474
503
* @param {Array } newActiveIndexes
475
504
*/
476
505
handleFocusChange : function ( newActiveIndexes ) {
477
- var i ;
478
506
var toDeactivate = $ ( this . currentActiveIndexes ) . not ( newActiveIndexes ) . get ( ) ;
479
507
var toActivate = $ ( newActiveIndexes ) . not ( this . currentActiveIndexes ) . get ( ) ;
480
508
481
- for ( i = 0 ; i < toDeactivate . length ; ++ i ) {
482
- $ ( this . items [ toDeactivate [ i ] ] ) . removeClass ( this . activeClass ) ;
483
- }
509
+ foreach ( this , toDeactivate , function ( _ , itemIndex ) {
510
+ $ ( this . items [ itemIndex ] ) . removeClass ( this . activeClass ) ;
511
+ } ) ;
484
512
485
- for ( i = 0 ; i < toActivate . length ; ++ i ) {
486
- $ ( this . items [ toActivate [ i ] ] ) . addClass ( this . activeClass ) ;
487
- }
513
+ foreach ( this , toActivate , function ( _ , itemIndex ) {
514
+ $ ( this . items [ itemIndex ] ) . addClass ( this . activeClass ) ;
515
+ } ) ;
488
516
489
517
this . currentActiveIndexes = newActiveIndexes ;
490
518
} ,
@@ -501,9 +529,9 @@ var Shira;
501
529
var newActiveIndexes = [ ] ;
502
530
503
531
if ( focus instanceof Array ) {
504
- for ( var i = 0 ; i < focus . length ; ++ i ) {
505
- newActiveIndexes . push ( focus [ i ] . index ) ;
506
- }
532
+ foreach ( this , focus , function ( _ , focus ) {
533
+ newActiveIndexes . push ( focus . index ) ;
534
+ } ) ;
507
535
} else {
508
536
newActiveIndexes . push ( focus . index ) ;
509
537
}
0 commit comments