PageSourceSearch

https://cdn.guideir.cn/Public/Cn/js/sticky.min.js

js guideir.cn collected 2026-09-24 12:37:58 UTC 15,048 bytes, 467 lines download raw bytes

1function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2
3/**
4 * Sticky.js
5 * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive.
6 *
7 * @version 1.2.0
8 * @author Rafal Galus <[email protected]>
9 * @website https://rgalus.github.io/sticky-js/
10 * @repo https://github.com/rgalus/sticky-js
11 * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE
12 */
13
14var Sticky = function () {
15  /**
16   * Sticky instance constructor
17   * @constructor
18   * @param {string} selector - Selector which we can find elements
19   * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}="" attributes)
20   */
21  function Sticky() {
22    var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
23    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
24
25    _classCallCheck(this, Sticky);
26
27    this.selector = selector;
28    this.elements = [];
29
30    this.version = '1.2.0';
31
32    this.vp = this.getViewportSize();
33    this.body = document.querySelector('body');
34
35    this.options = {
36      wrap: options.wrap || false,
37      marginTop: options.marginTop || 0,
38      stickyFor: options.stickyFor || 0,
39      stickyClass: options.stickyClass || null,
40      stickyContainer: options.stickyContainer || 'body'
41    };
42
43    this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
44
45    this.updateScrollTopPosition();
46    window.addEventListener('load', this.updateScrollTopPosition);
47    window.addEventListener('scroll', this.updateScrollTopPosition);
48
49    this.run();
50  }
51
52  /**
53   * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector
54   * @function
55   */
56
57
58  Sticky.prototype.run = function run() {
59    var _this = this;
60
61    // wait for page to be fully loaded
62    var pageLoaded = setInterval(function () {
63      if (document.readyState === 'complete') {
64        clearInterval(pageLoaded);
65
66        var elements = document.querySelectorAll(_this.selector);
67        _this.forEach(elements, function (element) {
68          return _this.renderElement(element);
69        });
70      }
71    }, 10);
72  };
73
74  /**
75   * Function that assign needed variables for sticky element, that are used in future for calculations and other
76   * @function
77   * @param {node} element - Element to be rendered
78   */
79
80
81  Sticky.prototype.renderElement = function renderElement(element) {
82    var _this2 = this;
83
84    // create container for variables needed in future
85    element.sticky = {};
86
87    // set default variables
88    element.sticky.active = false;
89
90    element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
91    element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
92    element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
93    element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap;
94    // @todo attribute for stickyContainer
95    // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
96    element.sticky.stickyContainer = this.options.stickyContainer;
97
98    element.sticky.container = this.getStickyContainer(element);
99    element.sticky.container.rect = this.getRectangle(element.sticky.container);
100
101    element.sticky.rect = this.getRectangle(element);
102
103    // fix when element is image that has not yet loaded and width, height = 0
104    if (element.tagName.toLowerCase() === 'img') {
105      element.onload = function () {
106        return element.sticky.rect = _this2.getRectangle(element);
107      };
108    }
109
110    if (element.sticky.wrap) {
111      this.wrapElement(element);
112    }
113
114    // activate rendered element
115    this.activate(element);
116  };
117
118  /**
119   * Wraps element into placeholder element
120   * @function
121   * @param {node} element - Element to be wrapped
122   */
123
124
125  Sticky.prototype.wrapElement = function wrapElement(element) {
126    element.insertAdjacentHTML('beforebegin', '<span></span>');
127    element.previousSibling.appendChild(element);
128  };
129
130  /**
131   * Function that activates element when specified conditions are met and then initalise events
132   * @function
133   * @param {node} element - Element to be activated
134   */
135
136
137  Sticky.prototype.activate = function activate(element) {
138    if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
139      element.sticky.active = true;
140    }
141
142    if (this.elements.indexOf(element) < 0) {
143      this.elements.push(element);
144    }
145
146    if (!element.sticky.resizeEvent) {
147      this.initResizeEvents(element);
148      element.sticky.resizeEvent = true;
149    }
150
151    if (!element.sticky.scrollEvent) {
152      this.initScrollEvents(element);
153      element.sticky.scrollEvent = true;
154    }
155
156    this.setPosition(element);
157  };
158
159  /**
160   * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener
161   * @function
162   * @param {node} element - Element for which resize events are initialised
163   */
164
165
166  Sticky.prototype.initResizeEvents = function initResizeEvents(element) {
167    var _this3 = this;
168
169    element.sticky.resizeListener = function () {
170      return _this3.onResizeEvents(element);
171    };
172    window.addEventListener('resize', element.sticky.resizeListener);
173  };
174
175  /**
176   * Removes element listener from resize event
177   * @function
178   * @param {node} element - Element from which listener is deleted
179   */
180
181
182  Sticky.prototype.destroyResizeEvents = function destroyResizeEvents(element) {
183    window.removeEventListener('resize', element.sticky.resizeListener);
184  };
185
186  /**
187   * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function
188   * @function
189   * @param {node} element - Element for which event function is fired
190   */
191
192
193  Sticky.prototype.onResizeEvents = function onResizeEvents(element) {
194    this.vp = this.getViewportSize();
195
196    element.sticky.rect = this.getRectangle(element);
197    element.sticky.container.rect = this.getRectangle(element.sticky.container);
198
199    if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
200      element.sticky.active = true;
201    } else if (element.sticky.rect.top + element.sticky.rect.height >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) {
202      element.sticky.active = false;
203    }
204
205    this.setPosition(element);
206  };
207
208  /**
209   * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener
210   * @function
211   * @param {node} element - Element for which scroll events are initialised
212   */
213
214
215  Sticky.prototype.initScrollEvents = function initScrollEvents(element) {
216    var _this4 = this;
217
218    element.sticky.scrollListener = function () {
219      return _this4.onScrollEvents(element);
220    };
221    window.addEventListener('scroll', element.sticky.scrollListener);
222  };
223
224  /**
225   * Removes element listener from scroll event
226   * @function
227   * @param {node} element - Element from which listener is deleted
228   */
229
230
231  Sticky.prototype.destroyScrollEvents = function destroyScrollEvents(element) {
232    window.removeEventListener('scroll', element.sticky.scrollListener);
233  };
234
235  /**
236   * Function which is fired when user scroll window. If element is active, function is invoking setPosition function
237   * @function
238   * @param {node} element - Element for which event function is fired
239   */
240
241
242  Sticky.prototype.onScrollEvents = function onScrollEvents(element) {
243    if (element.sticky.active) {
244      this.setPosition(element);
245    }
246  };
247
248  /**
249   * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window
250   * @function
251   * @param {node} element - Element that will be positioned if it's active
252   */
253
254
255  Sticky.prototype.setPosition = function setPosition(element) {
256    this.css(element, { position: '', width: '', top: '', left: '' });
257
258    if (this.vp.height < element.sticky.rect.height || !element.sticky.active) {
259      return;
260    }
261
262    if (!element.sticky.rect.width) {
263      element.sticky.rect = this.getRectangle(element);
264    }
265
266    if (element.sticky.wrap) {
267      this.css(element.parentNode, {
268        display: 'block',
269        width: element.sticky.rect.width + 'px',
270        height: element.sticky.rect.height + 'px'
271      });
272    }
273
274    if (element.sticky.rect.top === 0 && element.sticky.container === this.body) {
275      this.css(element, {
276        position: 'fixed',
277        top: element.sticky.rect.top + 'px',
278        left: element.sticky.rect.left + 'px',
279        width: element.sticky.rect.width + 'px'
280      });
281    } else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) {
282      this.css(element, {
283        position: 'fixed',
284        width: element.sticky.rect.width + 'px',
285        left: element.sticky.rect.left + 'px'
286      });
287
288      if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight) {
289
290        if (element.sticky.stickyClass) {
291          element.classList.remove(element.sticky.stickyClass);
292        }
293
294        this.css(element, {
295          top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height) + 'px' });
296      } else {
297        if (element.sticky.stickyClass) {
298          element.classList.add(element.sticky.stickyClass);
299        }
300
301        this.css(element, { top: element.sticky.marginTop + 'px' });
302      }
303    } else {
304      if (element.sticky.stickyClass) {
305        element.classList.remove(element.sticky.stickyClass);
306      }
307
308      this.css(element, { position: '', width: '', top: '', left: '' });
309
310      if (element.sticky.wrap) {
311        this.css(element.parentNode, { display: '', width: '', height: '' });
312      }
313    }
314  };
315
316  /**
317   * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active
318   * @function
319   */
320
321
322  Sticky.prototype.update = function update() {
323    var _this5 = this;
324
325    this.forEach(this.elements, function (element) {
326      element.sticky.rect = _this5.getRectangle(element);
327      element.sticky.container.rect = _this5.getRectangle(element.sticky.container);
328
329      _this5.activate(element);
330      _this5.setPosition(element);
331    });
332  };
333
334  /**
335   * Destroys sticky element, remove listeners
336   * @function
337   */
338
339
340  Sticky.prototype.destroy = function destroy() {
341    var _this6 = this;
342
343    this.forEach(this.elements, function (element) {
344      _this6.destroyResizeEvents(element);
345      _this6.destroyScrollEvents(element);
346      delete element.sticky;
347    });
348  };
349
350  /**
351   * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body)
352   * @function
353   * @param {node} element - Element which sticky container are looked for
354   * @return {node} element - Sticky container
355   */
356
357
358  Sticky.prototype.getStickyContainer = function getStickyContainer(element) {
359    var container = element.parentNode;
360
361    while (!container.hasAttribute('data-sticky-container') && !container.parentNode.querySelector(element.sticky.stickyContainer) && container !== this.body) {
362      container = container.parentNode;
363    }
364
365    return container;
366  };
367
368  /**
369   * Function that returns element rectangle & position (width, height, top, left)
370   * @function
371   * @param {node} element - Element which position & rectangle are returned
372   * @return {object}
373   */
374
375
376  Sticky.prototype.getRectangle = function getRectangle(element) {
377    this.css(element, { position: '', width: '', top: '', left: '' });
378
379    var width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth);
380    var height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight);
381
382    var top = 0;
383    var left = 0;
384
385    do {
386      top += element.offsetTop || 0;
387      left += element.offsetLeft || 0;
388      element = element.offsetParent;
389    } while (element);
390
391    return { top: top, left: left, width: width, height: height };
392  };
393
394  /**
395   * Function that returns viewport dimensions
396   * @function
397   * @return {object}
398   */
399
400
401  Sticky.prototype.getViewportSize = function getViewportSize() {
402    return {
403      width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
404      height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
405    };
406  };
407
408  /**
409   * Function that updates window scroll position
410   * @function
411   * @return {number}
412   */
413
414
415  Sticky.prototype.updateScrollTopPosition = function updateScrollTopPosition() {
416    this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;
417  };
418
419  /**
420   * Helper function for loops
421   * @helper
422   * @param {array}
423   * @param {function} callback - Callback function (no need for explanation)
424   */
425
426
427  Sticky.prototype.forEach = function forEach(array, callback) {
428    for (var i = 0, len = array.length; i < len; i++) {
429      callback(array[i]);
430    }
431  };
432
433  /**
434   * Helper function to add/remove css properties for specified element.
435   * @helper
436   * @param {node} element - DOM element
437   * @param {object} properties - CSS properties that will be added/removed from specified element
438   */
439
440
441  Sticky.prototype.css = function css(element, properties) {
442    for (var property in properties) {
443      if (properties.hasOwnProperty(property)) {
444        element.style[property] = properties[property];
445      }
446    }
447  };
448
449  return Sticky;
450}();
451
452/**
453 * Export function that supports AMD, CommonJS and Plain Browser.
454 */
455
456
457(function (root, factory) {
458  if (typeof exports !== 'undefined') {
459    module.exports = factory;
460  } else if (typeof define === 'function' && define.amd) {
461    define([], factory);
462  } else {
463    root.Sticky = factory;
464  }
465})(this, Sticky);
466
467

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.