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