PageSourceSearch

https://theexpat.com/modules/mod_jux_megamenu/assets/js/headroom.js

js theexpat.com collected 2026-10-02 12:44:22 UTC 8,924 bytes, 302 lines download raw bytes

1/*!
2 * headroom.js v0.4.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  }
108  Headroom.prototype = {
109    constructor : Headroom,
110  
111    /**
112     * Initialises the widget
113     */
114    init : function() {
115      if(!Headroom.cutsTheMustard) {
116        return;
117      }
118  
119      this.elem.classList.add(this.classes.initial);
120  
121      // defer event registration to handle browser 
122      // potentially restoring previous scroll position
123      setTimeout(this.attachEvent.bind(this), 100);
124  
125      return this;
126    },
127  
128    /**
129     * Unattaches events and removes any classes that were added
130     */
131    destroy : function() {
132      var classes = this.classes;
133  
134      this.initialised = false;
135      window.removeEventListener('scroll', this.debouncer, false);
136      this.elem.classList.remove(classes.unpinned, classes.pinned, classes.initial);
137    },
138  
139    /**
140     * Attaches the scroll event
141     * @private
142     */
143    attachEvent : function() {
144      if(!this.initialised){
145        this.lastKnownScrollY = this.getScrollY();
146        this.initialised = true;
147        window.addEventListener('scroll', this.debouncer, false);
148      }
149    },
150    
151    /**
152     * Unpins the header if it's currently pinned
153     */
154    unpin : function() {
155      var classList = this.elem.classList,
156        classes = this.classes;
157      
158      if(classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) {
159        classList.add(classes.unpinned);
160        classList.remove(classes.pinned);
161        this.onUnpin && this.onUnpin.call(this);
162      }
163    },
164  
165    /**
166     * Pins the header if it's currently unpinned
167     */
168    pin : function() {
169      var classList = this.elem.classList,
170        classes = this.classes;
171      
172      if(classList.contains(classes.unpinned)) {
173        classList.remove(classes.unpinned);
174        classList.add(classes.pinned);
175        this.onPin && this.onPin.call(this);
176      }
177    },
178  
179    /**
180     * Gets the Y scroll position
181     * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
182     * @return {Number} pixels the page has scrolled along the Y-axis
183     */
184    getScrollY : function() {
185      return (window.pageYOffset !== undefined)
186        ? window.pageYOffset
187        : (document.documentElement || document.body.parentNode || document.body).scrollTop;
188    },
189  
190    /**
191     * Gets the height of the viewport
192     * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
193     * @return {int} the height of the viewport in pixels
194     */
195    getViewportHeight : function () {
196      return window.innerHeight
197        || document.documentElement.clientHeight
198        || document.body.clientHeight;
199    },
200  
201    /**
202     * Gets the height of the document
203     * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
204     * @return {int} the height of the document in pixels
205     */
206    getDocumentHeight : function () {
207      var body = document.body,
208        documentElement = document.documentElement;
209  
210      return Math.max(
211          body.scrollHeight, documentElement.scrollHeight,
212          body.offsetHeight, documentElement.offsetHeight,
213          body.clientHeight, documentElement.clientHeight
214      );
215    },
216  
217    /**
218     * determines if the scroll position is outside of document boundaries
219     * @param  {int}  currentScrollY the current y scroll position
220     * @return {bool} true if out of bounds, false otherwise
221     */
222    isOutOfBounds : function (currentScrollY) {
223      var pastTop  = currentScrollY < 0,
224        pastBottom = currentScrollY + this.getViewportHeight() > this.getDocumentHeight();
225      
226      return pastTop || pastBottom;
227    },
228  
229    /**
230     * determines if the tolerance has been exceeded
231     * @param  {int} currentScrollY the current scroll y position
232     * @return {bool} true if tolerance exceeded, false otherwise
233     */
234    toleranceExceeded : function (currentScrollY) {
235      return Math.abs(currentScrollY-this.lastKnownScrollY) >= this.tolerance;
236    },
237  
238    /**
239     * determine if it is appropriate to unpin
240     * @param  {int} currentScrollY the current y scroll position
241     * @param  {bool} toleranceExceeded has the tolerance been exceeded?
242     * @return {bool} true if should unpin, false otherwise
243     */
244    shouldUnpin : function (currentScrollY, toleranceExceeded) {
245      var scrollingDown = currentScrollY > this.lastKnownScrollY,
246        pastOffset = currentScrollY >= this.offset;
247  
248      return scrollingDown && pastOffset && toleranceExceeded;
249    },
250  
251    /**
252     * determine if it is appropriate to pin
253     * @param  {int} currentScrollY the current y scroll position
254     * @param  {bool} toleranceExceeded has the tolerance been exceeded?
255     * @return {bool} true if should pin, false otherwise
256     */
257    shouldPin : function (currentScrollY, toleranceExceeded) {
258      var scrollingUp  = currentScrollY < this.lastKnownScrollY,
259        pastOffset = currentScrollY <= this.offset;
260  
261      return (scrollingUp && toleranceExceeded) || pastOffset;
262    },
263  
264    /**
265     * Handles updating the state of the widget
266     */
267    update : function() {
268      var currentScrollY  = this.getScrollY(),
269        toleranceExceeded = this.toleranceExceeded(currentScrollY);
270  
271      if(this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX
272        return;
273      }
274  
275      if(this.shouldUnpin(currentScrollY, toleranceExceeded)) {
276        this.unpin();
277      }
278      else if(this.shouldPin(currentScrollY, toleranceExceeded)) {
279        this.pin();
280      }
281  
282      this.lastKnownScrollY = currentScrollY;
283    }
284  };
285  /**
286   * Default options
287   * @type {Object}
288   */
289  Headroom.options = {
290    tolerance : 0,
291    offset: 0,
292    classes : {
293      pinned : 'headroom--pinned',
294      unpinned : 'headroom--unpinned',
295      initial : 'headroom'
296    }
297  };
298  Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList;
299
300  window.Headroom = Headroom;
301
302}(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.