1/*! 2 * headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it 3 * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js 4 * License: MIT 5 */ 6 7(function(window, document) { 8 9 'use strict'; 10 11 /* exported features */ 12 13 var features = { 14 bind : !!(function(){}.bind), 15 classList : 'classList' in document.documentElement, 16 rAF : !!(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame) 17 }; 18 window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; 19 20 /** 21 * Handles debouncing of events via requestAnimationFrame 22 * @see http://www.html5rocks.com/en/tutorials/speed/animations/ 23 * @param {Function} callback The callback to handle whichever event 24 */ 25 function Debouncer (callback) { 26 this.callback = callback; 27 this.ticking = false; 28 } 29 Debouncer.prototype = { 30 constructor : Debouncer, 31 32 /** 33 * dispatches the event to the supplied callback 34 * @private 35 */ 36 update : function() { 37 this.callback && this.callback(); 38 this.ticking = false; 39 }, 40 41 /** 42 * ensures events don't get stacked 43 * @private 44 */ 45 requestTick : function() { 46 if(!this.ticking) { 47 requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); 48 this.ticking = true; 49 } 50 }, 51 52 /** 53 * Attach this as the event listeners 54 */ 55 handleEvent : function() { 56 this.requestTick(); 57 } 58 }; 59 /** 60 * Check if object is part of the DOM 61 * @constructor 62 * @param {Object} obj element to check 63 */ 64 function isDOMElement(obj) { 65 return obj && typeof window !== 'undefined' && (obj === window || obj.nodeType); 66 } 67 68 /** 69 * Helper function for extending objects 70 */ 71 function extend (object /*, objectN ... */) { 72 if(arguments.length <= 0) { 73 throw new Error('Missing arguments in extend function'); 74 } 75 76 var result = object || {}, 77 key, 78 i; 79 80 for (i = 1; i < arguments.length; i++) { 81 var replacement = arguments[i] || {}; 82 83 for (key in replacement) { 84 // Recurse into object except if the object is a DOM element 85 if(typeof result[key] === 'object' && ! isDOMElement(result[key])) { 86 result[key] = extend(result[key], replacement[key]); 87 } 88 else { 89 result[key] = result[key] || replacement[key]; 90 } 91 } 92 } 93 94 return result; 95 } 96 97 /** 98 * Helper function for normalizing tolerance option to object format 99 */ 100 function normalizeTolerance (t) { 101 return t === Object(t) ? t : { down : t, up : t }; 102 } 103 104 /** 105 * UI enhancement for fixed headers. 106 * Hides header when scrolling down 107 * Shows header when scrolling up 108 * @constructor 109 * @param {DOMElement} elem the header element 110 * @param {Object} options options for the widget 111 */ 112 function Headroom (elem, options) { 113 options = extend(options, Headroom.options); 114 115 this.lastKnownScrollY = 0; 116 this.elem = elem; 117 this.debouncer = new Debouncer(this.update.bind(this)); 118 this.tolerance = normalizeTolerance(options.tolerance); 119 this.classes = options.classes; 120 this.offset = options.offset; 121 this.scroller = options.scroller; 122 this.initialised = false; 123 this.onPin = options.onPin; 124 this.onUnpin = options.onUnpin; 125 this.onTop = options.onTop; 126 this.onNotTop = options.onNotTop; 127 } 128 Headroom.prototype = { 129 constructor : Headroom, 130 131 /** 132 * Initialises the widget 133 */ 134 init : function() { 135 if(!Headroom.cutsTheMustard) { 136 return; 137 } 138 139 this.elem.classList.add(this.classes.initial); 140 141 // defer event registration to handle browser 142 // potentially restoring previous scroll position 143 setTimeout(this.attachEvent.bind(this), 100); 144 145 return this; 146 }, 147 148 /** 149 * Unattaches events and removes any classes that were added 150 */ 151 destroy : function() { 152 var classes = this.classes; 153
154 this.initialised = false; 155 this.elem.classList.remove(classes.unpinned, classes.pinned, classes.top, classes.initial); 156 this.scroller.removeEventListener('scroll', this.debouncer, false); 157 }, 158 159 /** 160 * Attaches the scroll event 161 * @private 162 */ 163 attachEvent : function() { 164 if(!this.initialised){ 165 this.lastKnownScrollY = this.getScrollY(); 166 this.initialised = true; 167 this.scroller.addEventListener('scroll', this.debouncer, false); 168 169 this.debouncer.handleEvent(); 170 } 171 }, 172 173 /** 174 * Unpins the header if it's currently pinned 175 */ 176 unpin : function() { 177 var classList = this.elem.classList, 178 classes = this.classes; 179 180 if(classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) { 181 classList.add(classes.unpinned); 182 classList.remove(classes.pinned); 183 this.onUnpin && this.onUnpin.call(this); 184 } 185 }, 186 187 /** 188 * Pins the header if it's currently unpinned 189 */ 190 pin : function() { 191 var classList = this.elem.classList, 192 classes = this.classes; 193 194 if(classList.contains(classes.unpinned)) { 195 classList.remove(classes.unpinned); 196 classList.add(classes.pinned); 197 this.onPin && this.onPin.call(this); 198 } 199 }, 200 201 /** 202 * Handles the top states 203 */ 204 top : function() { 205 var classList = this.elem.classList, 206 classes = this.classes; 207 208 if(!classList.contains(classes.top)) { 209 classList.add(classes.top); 210 classList.remove(classes.notTop); 211 this.onTop && this.onTop.call(this); 212 } 213 }, 214 215 /** 216 * Handles the not top state 217 */ 218 notTop : function() { 219 var classList = this.elem.classList, 220 classes = this.classes; 221 222 if(!classList.contains(classes.notTop)) { 223 classList.add(classes.notTop); 224 classList.remove(classes.top); 225 this.onNotTop && this.onNotTop.call(this); 226 } 227 }, 228 229 /** 230 * Gets the Y scroll position 231 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY 232 * @return {Number} pixels the page has scrolled along the Y-axis 233 */ 234 getScrollY : function() { 235 return (this.scroller.pageYOffset !== undefined) 236 ? this.scroller.pageYOffset 237 : (this.scroller.scrollTop !== undefined) 238 ? this.scroller.scrollTop 239 : (document.documentElement || document.body.parentNode || document.body).scrollTop; 240 }, 241 242 /** 243 * Gets the height of the viewport 244 * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript 245 * @return {int} the height of the viewport in pixels 246 */ 247 getViewportHeight : function () { 248 return window.innerHeight 249 || document.documentElement.clientHeight 250 || document.body.clientHeight; 251 }, 252 253 /** 254 * Gets the height of the document 255 * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/ 256 * @return {int} the height of the document in pixels 257 */ 258 getDocumentHeight : function () { 259 var body = document.body, 260 documentElement = document.documentElement; 261 262 return Math.max( 263 body.scrollHeight, documentElement.scrollHeight, 264 body.offsetHeight, documentElement.offsetHeight, 265 body.clientHeight, documentElement.clientHeight 266 ); 267 }, 268 269 /** 270 * Gets the height of the DOM element 271 * @param {Object} elm the element to calculate the height of which 272 * @return {int} the height of the element in pixels 273 */ 274 getElementHeight : function (elm) { 275 return Math.max( 276 elm.scrollHeight, 277 elm.offsetHeight, 278 elm.clientHeight 279 ); 280 }, 281 282 /** 283 * Gets the height of the scroller element 284 * @return {int} the height of the scroller element in pixels 285 */ 286 getScrollerHeight : function () { 287 return (this.scroller === window || this.scroller === document.body) 288 ? this.getDocumentHeight() 289 : this.getElementHeight(this.scroller); 290 }, 291 292 /** 293 * determines if the scroll position is outside of document boundaries 294 * @param {int} currentScrollY the current y scroll position 295 * @return {bool} true if out of bounds, false otherwise 296 */ 297 isOutOfBounds : function (currentScrollY) { 298 var pastTop = currentScrollY < 0,
299 pastBottom = currentScrollY + this.getViewportHeight() > this.getScrollerHeight(); 300 301 return pastTop || pastBottom; 302 }, 303 304 /** 305 * determines if the tolerance has been exceeded 306 * @param {int} currentScrollY the current scroll y position 307 * @return {bool} true if tolerance exceeded, false otherwise 308 */ 309 toleranceExceeded : function (currentScrollY, direction) { 310 return Math.abs(currentScrollY-this.lastKnownScrollY) >= this.tolerance[direction]; 311 }, 312 313 /** 314 * determine if it is appropriate to unpin 315 * @param {int} currentScrollY the current y scroll position 316 * @param {bool} toleranceExceeded has the tolerance been exceeded? 317 * @return {bool} true if should unpin, false otherwise 318 */ 319 shouldUnpin : function (currentScrollY, toleranceExceeded) { 320 var scrollingDown = currentScrollY > this.lastKnownScrollY, 321 pastOffset = currentScrollY >= this.offset; 322 323 return scrollingDown && pastOffset && toleranceExceeded; 324 }, 325 326 /** 327 * determine if it is appropriate to pin 328 * @param {int} currentScrollY the current y scroll position 329 * @param {bool} toleranceExceeded has the tolerance been exceeded? 330 * @return {bool} true if should pin, false otherwise 331 */ 332 shouldPin : function (currentScrollY, toleranceExceeded) { 333 var scrollingUp = currentScrollY < this.lastKnownScrollY, 334 pastOffset = currentScrollY <= this.offset; 335 336 return (scrollingUp && toleranceExceeded) || pastOffset; 337 }, 338 339 /** 340 * Handles updating the state of the widget 341 */ 342 update : function() { 343 var currentScrollY = this.getScrollY(), 344 scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up', 345 toleranceExceeded = this.toleranceExceeded(currentScrollY, scrollDirection); 346 347 if(this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX 348 return; 349 } 350 351 if (currentScrollY <= this.offset ) { 352 this.top(); 353 } else { 354 this.notTop(); 355 } 356 357 if(this.shouldUnpin(currentScrollY, toleranceExceeded)) { 358 this.unpin(); 359 } 360 else if(this.shouldPin(currentScrollY, toleranceExceeded)) { 361 this.pin(); 362 } 363 364 this.lastKnownScrollY = currentScrollY; 365 } 366 }; 367 /** 368 * Default options 369 * @type {Object} 370 */ 371 Headroom.options = { 372 tolerance : { 373 up : 0, 374 down : 0 375 }, 376 offset : 0, 377 scroller: window, 378 classes : { 379 pinned : 'headroom--pinned', 380 unpinned : 'headroom--unpinned', 381 top : 'headroom--top', 382 notTop : 'headroom--not-top', 383 initial : 'headroom' 384 } 385 }; 386 Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList; 387 388 window.Headroom = Headroom; 389 390}(window, document));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.