PageSourceSearch

https://www.absolutelyfresh.nl/wp-content/themes/absolutelyfresh/js/headroom.js

js absolutelyfresh.nl collected 2026-10-08 19:42:42 UTC 9,934 bytes, 342 lines download raw bytes

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.