PageSourceSearch

https://nyapi.ru/g/templates/shop2/2.66.2/js/headeroom.js

js nyapi.ru collected 2026-10-07 06:49:26 UTC 14,001 bytes, 461 lines download raw bytes

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

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.