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 : {down: t, up: t}; 113 } 114 115 /** 116 * UI enhancement for fixed headers. 117 * Hides header when scrolling down 118 * Shows header when scrolling up 119 * @constructor 120 * @param {DOMElement} elem the header element 121 * @param {Object} options options for the widget 122 */ 123 function Headroom(elem, options) { 124 options = extend(options, Headroom.options); 125 126 this.lastKnownScrollY = 0; 127 this.elem = elem; 128 this.tolerance = normalizeTolerance(options.tolerance); 129 this.classes = options.classes; 130 this.offset = options.offset; 131 this.scroller = options.scroller; 132 this.initialised = false; 133 this.onPin = options.onPin; 134 this.onUnpin = options.onUnpin; 135 this.onTop = options.onTop; 136 this.onNotTop = options.onNotTop; 137 this.onBottom = options.onBottom; 138 this.onNotBottom = options.onNotBottom; 139 } 140 Headroom.prototype = { 141 constructor: Headroom, 142 143 /** 144 * Initialises the widget 145 */ 146 init: function () { 147 if (!Headroom.cutsTheMustard) { 148 return; 149 } 150 151 this.debouncer = new Debouncer(this.update.bind(this)); 152 this.elem.classList.add(this.classes.initial); 153 154 // defer event registration to handle browser 155 // potentially restoring previous scroll position 156 setTimeout(this.attachEvent.bind(this), 100); 157 158 return this; 159 }, 160 161 /** 162 * Unattaches events and removes any classes that were added 163 */ 164 destroy: function () { 165 var classes = this.classes; 166
167 this.initialised = false; 168 169 for (var key in classes) { 170 if (classes.hasOwnProperty(key)) { 171 this.elem.classList.remove(classes[key]); 172 } 173 } 174 175 this.scroller.removeEventListener('scroll', this.debouncer, false); 176 }, 177 178 /** 179 * Attaches the scroll event 180 * @private 181 */ 182 attachEvent: function () { 183 if (!this.initialised) { 184 this.lastKnownScrollY = this.getScrollY(); 185 this.initialised = true; 186 this.scroller.addEventListener('scroll', this.debouncer, false); 187 188 this.debouncer.handleEvent(); 189 } 190 }, 191 192 /** 193 * Unpins the header if it's currently pinned 194 */ 195 unpin: function () { 196 var classList = this.elem.classList, 197 classes = this.classes; 198 199 if (classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) { 200 classList.add(classes.unpinned); 201 classList.remove(classes.pinned); 202 this.onUnpin && this.onUnpin.call(this); 203 } 204 }, 205 206 /** 207 * Pins the header if it's currently unpinned 208 */ 209 pin: function () { 210 var classList = this.elem.classList, 211 classes = this.classes; 212 213 if (classList.contains(classes.unpinned)) { 214 classList.remove(classes.unpinned); 215 classList.add(classes.pinned); 216 this.onPin && this.onPin.call(this); 217 } 218 }, 219 220 /** 221 * Handles the top states 222 */ 223 top: function () { 224 var classList = this.elem.classList, 225 classes = this.classes; 226 227 if (!classList.contains(classes.top)) { 228 classList.add(classes.top); 229 classList.remove(classes.notTop); 230 this.onTop && this.onTop.call(this); 231 } 232 }, 233 234 /** 235 * Handles the not top state 236 */ 237 notTop: function () { 238 var classList = this.elem.classList, 239 classes = this.classes; 240 241 if (!classList.contains(classes.notTop)) { 242 classList.add(classes.notTop); 243 classList.remove(classes.top); 244 this.onNotTop && this.onNotTop.call(this); 245 } 246 }, 247 248 bottom: function () { 249 var classList = this.elem.classList, 250 classes = this.classes; 251 252 if (!classList.contains(classes.bottom)) { 253 classList.add(classes.bottom); 254 classList.remove(classes.notBottom); 255 this.onBottom && this.onBottom.call(this); 256 } 257 }, 258 259 /** 260 * Handles the not top state 261 */ 262 notBottom: function () { 263 var classList = this.elem.classList, 264 classes = this.classes; 265 266 if (!classList.contains(classes.notBottom)) { 267 classList.add(classes.notBottom); 268 classList.remove(classes.bottom); 269 this.onNotBottom && this.onNotBottom.call(this); 270 } 271 }, 272 273 /** 274 * Gets the Y scroll position 275 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY 276 * @return {Number} pixels the page has scrolled along the Y-axis 277 */ 278 getScrollY: function () { 279 return (this.scroller.pageYOffset !== undefined) 280 ? this.scroller.pageYOffset 281 : (this.scroller.scrollTop !== undefined) 282 ? this.scroller.scrollTop 283 : (document.documentElement || document.body.parentNode || document.body).scrollTop; 284 }, 285 286 /** 287 * Gets the height of the viewport 288 * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript 289 * @return {int} the height of the viewport in pixels 290 */
291 getViewportHeight: function () { 292 return window.innerHeight 293 || document.documentElement.clientHeight 294 || document.body.clientHeight; 295 }, 296 297 /** 298 * Gets the physical height of the DOM element 299 * @param {Object} elm the element to calculate the physical height of which 300 * @return {int} the physical height of the element in pixels 301 */ 302 getElementPhysicalHeight: function (elm) { 303 return Math.max( 304 elm.offsetHeight, 305 elm.clientHeight 306 ); 307 }, 308 309 /** 310 * Gets the physical height of the scroller element 311 * @return {int} the physical height of the scroller element in pixels 312 */ 313 getScrollerPhysicalHeight: function () { 314 return (this.scroller === window || this.scroller === document.body) 315 ? this.getViewportHeight() 316 : this.getElementPhysicalHeight(this.scroller); 317 }, 318 319 /** 320 * Gets the height of the document 321 * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/ 322 * @return {int} the height of the document in pixels 323 */ 324 getDocumentHeight: function () { 325 var body = document.body, 326 documentElement = document.documentElement; 327 328 return Math.max( 329 body.scrollHeight, documentElement.scrollHeight, 330 body.offsetHeight, documentElement.offsetHeight, 331 body.clientHeight, documentElement.clientHeight 332 ); 333 }, 334 335 /** 336 * Gets the height of the DOM element 337 * @param {Object} elm the element to calculate the height of which 338 * @return {int} the height of the element in pixels 339 */ 340 getElementHeight: function (elm) { 341 return Math.max( 342 elm.scrollHeight, 343 elm.offsetHeight, 344 elm.clientHeight 345 ); 346 }, 347 348 /** 349 * Gets the height of the scroller element 350 * @return {int} the height of the scroller element in pixels 351 */ 352 getScrollerHeight: function () { 353 return (this.scroller === window || this.scroller === document.body) 354 ? this.getDocumentHeight() 355 : this.getElementHeight(this.scroller); 356 }, 357 358 /** 359 * determines if the scroll position is outside of document boundaries 360 * @param {int} currentScrollY the current y scroll position 361 * @return {bool} true if out of bounds, false otherwise 362 */ 363 isOutOfBounds: function (currentScrollY) { 364 var pastTop = currentScrollY < 0, 365 pastBottom = currentScrollY + this.getScrollerPhysicalHeight() > this.getScrollerHeight(); 366 367 return pastTop || pastBottom; 368 }, 369 370 /** 371 * determines if the tolerance has been exceeded 372 * @param {int} currentScrollY the current scroll y position 373 * @return {bool} true if tolerance exceeded, false otherwise 374 */ 375 toleranceExceeded: function (currentScrollY, direction) { 376 return Math.abs(currentScrollY - this.lastKnownScrollY) >= this.tolerance[direction]; 377 }, 378 379 /** 380 * determine if it is appropriate to unpin 381 * @param {int} currentScrollY the current y scroll position 382 * @param {bool} toleranceExceeded has the tolerance been exceeded? 383 * @return {bool} true if should unpin, false otherwise 384 */ 385 shouldUnpin: function (currentScrollY, toleranceExceeded) { 386 var scrollingDown = currentScrollY > this.lastKnownScrollY, 387 pastOffset = currentScrollY >= this.offset; 388 389 return scrollingDown && pastOffset && toleranceExceeded; 390 }, 391 392 /** 393 * determine if it is appropriate to pin 394 * @param {int} currentScrollY the current y scroll position 395 * @param {bool} toleranceExceeded has the tolerance been exceeded? 396 * @return {bool} true if should pin, false otherwise 397 */ 398 shouldPin: function (currentScrollY, toleranceExceeded) { 399 var scrollingUp = currentScrollY < this.lastKnownScrollY, 400 pastOffset = currentScrollY <= this.offset; 401 402 return (scrollingUp && toleranceExceeded) || pastOffset; 403 }, 404 405 /** 406 * Handles updating the state of the widget 407 */ 408 update: function () {
409 var currentScrollY = this.getScrollY(), 410 scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up', 411 toleranceExceeded = this.toleranceExceeded(currentScrollY, scrollDirection); 412 413 if (this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX 414 return; 415 } 416 417 if (currentScrollY <= this.offset) { 418 this.top(); 419 } else { 420 this.notTop(); 421 } 422 423 if (currentScrollY + this.getViewportHeight() >= this.getScrollerHeight()) { 424 this.bottom(); 425 } else { 426 this.notBottom(); 427 } 428 429 if (this.shouldUnpin(currentScrollY, toleranceExceeded)) { 430 this.unpin(); 431 } else if (this.shouldPin(currentScrollY, toleranceExceeded)) { 432 this.pin(); 433 } 434 435 this.lastKnownScrollY = currentScrollY; 436 } 437 }; 438 /** 439 * Default options 440 * @type {Object} 441 */ 442 Headroom.options = { 443 tolerance: { 444 up: 0, 445 down: 0 446 }, 447 offset: 0, 448 scroller: window, 449 classes: { 450 pinned: 'headroom--pinned', 451 unpinned: 'headroom--unpinned', 452 top: 'headroom--top', 453 notTop: 'headroom--not-top', 454 bottom: 'headroom--bottom', 455 notBottom: 'headroom--not-bottom', 456 initial: 'headroom' 457 } 458 }; 459 Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList; 460 461 return Headroom; 462})); 463 464(function ($) { 465 466 if (!$) { 467 return; 468 } 469 470 //////////// 471 // Plugin // 472 //////////// 473 474 $.fn.headroom = function (option) { 475 return this.each(function () { 476 var $this = $(this), 477 data = $this.data('headroom'), 478 options = typeof option === 'object' && option; 479 480 options = $.extend(true, {}, Headroom.options, options); 481 482 if (!data) { 483 data = new Headroom(this, options); 484 data.init(); 485 $this.data('headroom', data); 486 } 487 if (typeof option === 'string') { 488 data[option](); 489 490 if (option === 'destroy') { 491 $this.removeData('headroom'); 492 } 493 } 494 }); 495 }; 496 497 ////////////// 498 // Data API // 499 ////////////// 500 501 $('[data-headroom]').each(function () { 502 var $this = $(this); 503 $this.headroom($this.data()); 504 }); 505 506}(window.Zepto || window.jQuery));
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.