1/*! 2 * headroom.js v0.5.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 * Helper function for extending objects 61 */ 62 function extend (object /*, objectN ... */) { 63 if(arguments.length <= 0) { 64 throw new Error('Missing arguments in extend function'); 65 } 66 67 var result = object || {}, 68 key, 69 i; 70 71 for (i = 1; i < arguments.length; i++) { 72 var replacement = arguments[i] || {}; 73 74 for (key in replacement) { 75 if(typeof result[key] === 'object') { 76 result[key] = extend(result[key], replacement[key]); 77 } 78 else { 79 result[key] = result[key] || replacement[key]; 80 } 81 } 82 } 83 84 return result; 85 } 86 87 /** 88 * UI enhancement for fixed headers. 89 * Hides header when scrolling down 90 * Shows header when scrolling up 91 * @constructor 92 * @param {DOMElement} elem the header element 93 * @param {Object} options options for the widget 94 */ 95 function Headroom (elem, options) { 96 options = extend(options, Headroom.options); 97 98 this.lastKnownScrollY = 0; 99 this.elem = elem; 100 this.debouncer = new Debouncer(this.update.bind(this)); 101 this.tolerance = options.tolerance; 102 this.classes = options.classes; 103 this.offset = options.offset; 104 this.initialised = false; 105 this.onPin = options.onPin; 106 this.onUnpin = options.onUnpin; 107 this.onTop = options.onTop; 108 this.onNotTop = options.onNotTop; 109 } 110 Headroom.prototype = { 111 constructor : Headroom, 112 113 /** 114 * Initialises the widget 115 */ 116 init : function() { 117 if(!Headroom.cutsTheMustard) { 118 return; 119 } 120 121 this.elem.classList.add(this.classes.initial); 122 123 // defer event registration to handle browser 124 // potentially restoring previous scroll position 125 setTimeout(this.attachEvent.bind(this), 100); 126 127 return this; 128 }, 129 130 /** 131 * Unattaches events and removes any classes that were added 132 */ 133 destroy : function() { 134 var classes = this.classes; 135 136 this.initialised = false; 137 window.removeEventListener('scroll', this.debouncer, false); 138 this.elem.classList.remove(classes.unpinned, classes.pinned, classes.top, classes.initial); 139 }, 140 141 /** 142 * Attaches the scroll event 143 * @private 144 */ 145 attachEvent : function() { 146 if(!this.initialised){ 147 this.lastKnownScrollY = this.getScrollY(); 148 this.initialised = true; 149 window.addEventListener('scroll', this.debouncer, false); 150 151 this.debouncer.handleEvent(); 152 } 153 }, 154 155 /** 156 * Unpins the header if it's currently pinned 157 */ 158 unpin : function() { 159 var classList = this.elem.classList, 160 classes = this.classes; 161
162 if(classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) { 163 classList.add(classes.unpinned); 164 classList.remove(classes.pinned); 165 this.onUnpin && this.onUnpin.call(this); 166 } 167 }, 168 169 /** 170 * Pins the header if it's currently unpinned 171 */ 172 pin : function() { 173 var classList = this.elem.classList, 174 classes = this.classes; 175 176 if(classList.contains(classes.unpinned)) { 177 classList.remove(classes.unpinned); 178 classList.add(classes.pinned); 179 this.onPin && this.onPin.call(this); 180 } 181 }, 182 183 /** 184 * Handles the top states 185 */ 186 top : function() { 187 var classList = this.elem.classList, 188 classes = this.classes; 189 190 if(!classList.contains(classes.top)) { 191 classList.add(classes.top); 192 classList.remove(classes.notTop); 193 this.onTop && this.onTop.call(this); 194 } 195 }, 196 197 /** 198 * Handles the not top state 199 */ 200 notTop : function() { 201 var classList = this.elem.classList, 202 classes = this.classes; 203 204 if(!classList.contains(classes.notTop)) { 205 classList.add(classes.notTop); 206 classList.remove(classes.top); 207 this.onNotTop && this.onNotTop.call(this); 208 } 209 }, 210 211 /** 212 * Gets the Y scroll position 213 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY 214 * @return {Number} pixels the page has scrolled along the Y-axis 215 */ 216 getScrollY : function() { 217 return (window.pageYOffset !== undefined) 218 ? window.pageYOffset 219 : (document.documentElement || document.body.parentNode || document.body).scrollTop; 220 }, 221 222 /** 223 * Gets the height of the viewport 224 * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript 225 * @return {int} the height of the viewport in pixels 226 */ 227 getViewportHeight : function () { 228 return window.innerHeight 229 || document.documentElement.clientHeight 230 || document.body.clientHeight; 231 }, 232 233 /** 234 * Gets the height of the document 235 * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/ 236 * @return {int} the height of the document in pixels 237 */ 238 getDocumentHeight : function () { 239 var body = document.body, 240 documentElement = document.documentElement; 241 242 return Math.max( 243 body.scrollHeight, documentElement.scrollHeight, 244 body.offsetHeight, documentElement.offsetHeight, 245 body.clientHeight, documentElement.clientHeight 246 ); 247 }, 248 249 /** 250 * determines if the scroll position is outside of document boundaries 251 * @param {int} currentScrollY the current y scroll position 252 * @return {bool} true if out of bounds, false otherwise 253 */ 254 isOutOfBounds : function (currentScrollY) { 255 var pastTop = currentScrollY < 0, 256 pastBottom = currentScrollY + this.getViewportHeight() > this.getDocumentHeight(); 257 258 return pastTop || pastBottom; 259 }, 260 261 /** 262 * determines if the tolerance has been exceeded 263 * @param {int} currentScrollY the current scroll y position 264 * @return {bool} true if tolerance exceeded, false otherwise 265 */ 266 toleranceExceeded : function (currentScrollY) { 267 return Math.abs(currentScrollY-this.lastKnownScrollY) >= this.tolerance; 268 }, 269 270 /** 271 * determine if it is appropriate to unpin 272 * @param {int} currentScrollY the current y scroll position 273 * @param {bool} toleranceExceeded has the tolerance been exceeded? 274 * @return {bool} true if should unpin, false otherwise 275 */ 276 shouldUnpin : function (currentScrollY, toleranceExceeded) { 277 var scrollingDown = currentScrollY > this.lastKnownScrollY, 278 pastOffset = currentScrollY >= this.offset; 279 280 return scrollingDown && pastOffset && toleranceExceeded; 281 }, 282 283 /** 284 * determine if it is appropriate to pin 285 * @param {int} currentScrollY the current y scroll position 286 * @param {bool} toleranceExceeded has the tolerance been exceeded? 287 * @return {bool} true if should pin, false otherwise 288 */ 289 shouldPin : function (currentScrollY, toleranceExceeded) { 290 var scrollingUp = currentScrollY < this.lastKnownScrollY, 291 pastOffset = currentScrollY <= this.offset; 292 293 return (scrollingUp && toleranceExceeded) || pastOffset; 294 }, 295 296 /** 297 * Handles updating the state of the widget 298 */ 299 update : function() {
300 var currentScrollY = this.getScrollY(), 301 toleranceExceeded = this.toleranceExceeded(currentScrollY); 302 303 if(this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX 304 return; 305 } 306 307 if (currentScrollY <= this.offset ) { 308 this.top(); 309 } else { 310 this.notTop(); 311 } 312 313 if(this.shouldUnpin(currentScrollY, toleranceExceeded)) { 314 this.unpin(); 315 } 316 else if(this.shouldPin(currentScrollY, toleranceExceeded)) { 317 this.pin(); 318 } 319 320 this.lastKnownScrollY = currentScrollY; 321 } 322 }; 323 /** 324 * Default options 325 * @type {Object} 326 */ 327 Headroom.options = { 328 tolerance : 100, 329 offset: 0, 330 classes : { 331 pinned : 'headroom--pinned', 332 unpinned : 'headroom--unpinned', 333 top: 'headroom--top', 334 notTop: 'headroom--not-top', 335 initial : 'headroom' 336 } 337 }; 338 Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList; 339 340 window.Headroom = Headroom; 341 342}(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.