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