/** stickybits - Stickybits is a lightweight alternative to `position: sticky` polyfills @version v3.7.9 @link https://github.com/yowainwright/stickybits#readme @author Jeff Wainwright (https://jeffry.in) @license MIT **/ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.stickybits = factory()); }(this, (function () { 'use strict'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* STICKYBITS πŸ’‰ -------- > a lightweight alternative to `position: sticky` polyfills 🍬 -------- - each method is documented above it our view the readme - Stickybits does not manage polymorphic functionality (position like properties) * polymorphic functionality: (in the context of describing Stickybits) means making things like `position: sticky` be loosely supported with position fixed. It also means that features like `useStickyClasses` takes on styles like `position: fixed`. -------- defaults πŸ”Œ -------- - version = `package.json` version - userAgent = viewer browser agent - target = DOM element selector - noStyles = boolean - offset = number - parentClass = 'string' - scrollEl = window || DOM element selector || DOM element - stickyClass = 'string' - stuckClass = 'string' - useStickyClasses = boolean - useFixed = boolean - useGetBoundingClientRect = boolean - verticalPosition = 'string' - applyStyle = function -------- propsπŸ”Œ -------- - p = props {object} -------- instance note -------- - stickybits parent methods return this - stickybits instance methods return an instance item -------- nomenclature -------- - target => el => e - props => o || p - instance => item => it -------- methods -------- - .definePosition = defines sticky or fixed - .addInstance = an array of objects for each Stickybits Target - .getClosestParent = gets the parent for non-window scroll - .getTopPosition = gets the element top pixel position from the viewport - .computeScrollOffsets = computes scroll position - .toggleClasses = older browser toggler - .manageState = manages sticky state - .removeInstance = removes an instance - .cleanup = removes all Stickybits instances and cleans up dom from stickybits */ var Stickybits = /*#__PURE__*/function () { function Stickybits(target, obj) { var _this = this; var o = typeof obj !== 'undefined' ? obj : {}; this.version = '3.7.9'; this.userAgent = window.navigator.userAgent || 'no `userAgent` provided by the browser'; this.props = { customStickyChangeNumber: o.customStickyChangeNumber || null, noStyles: o.noStyles || false, stickyBitStickyOffset: o.stickyBitStickyOffset || 0, parentClass: o.parentClass || 'js-stickybit-parent', scrollEl: typeof o.scrollEl === 'string' ? document.querySelector(o.scrollEl) : o.scrollEl || window, stickyClass: o.stickyClass || 'js-is-sticky', stuckClass: o.stuckClass || 'js-is-stuck', stickyChangeClass: o.stickyChangeClass || 'js-is-sticky--change', useStickyClasses: o.useStickyClasses || false, useFixed: o.useFixed || false, useGetBoundingClientRect: o.useGetBoundingClientRect || false, verticalPosition: o.verticalPosition || 'top', applyStyle: o.applyStyle || function (item, style) { return _this.applyStyle(item, style); } }; /* define positionVal after the setting of props, because definePosition looks at the props.useFixed ---- - uses a computed (`.definePosition()`) - defined the position */ this.props.positionVal = this.definePosition() || 'fixed'; this.instances = []; var _this$props = this.props, positionVal = _this$props.positionVal, verticalPosition = _this$props.verticalPosition, noStyles = _this$props.noStyles, stickyBitStickyOffset = _this$props.stickyBitStickyOffset; var verticalPositionStyle = verticalPosition === 'top' && !noStyles ? stickyBitStickyOffset + "px" : ''; var positionStyle = positionVal !== 'fixed' ? positionVal : ''; this.els = typeof target === 'string' ? document.querySelectorAll(target) : target; if (!('length' in this.els)) this.els = [this.els]; for (var i = 0; i < this.els.length; i++) { var _styles; var el = this.els[i]; var instance = this.addInstance(el, this.props); // set vertical position this.props.applyStyle({ styles: (_styles = {}, _styles[verticalPosition] = verticalPositionStyle, _styles.position = positionStyle, _styles), classes: {} }, instance); this.manageState(instance); // instances are an array of objects this.instances.push(instance); } } /* setStickyPosition βœ”οΈ -------- β€” most basic thing stickybits does => checks to see if position sticky is supported => defined the position to be used => stickybits works accordingly */ var _proto = Stickybits.prototype; _proto.definePosition = function definePosition() { var stickyProp; if (this.props.useFixed) { stickyProp = 'fixed'; } else { var prefix = ['', '-o-', '-webkit-', '-moz-', '-ms-']; var test = document.head.style; for (var i = 0; i < prefix.length; i += 1) { test.position = prefix[i] + "sticky"; } stickyProp = test.position ? test.position : 'fixed'; test.position = ''; } return stickyProp; } /* addInstance βœ”οΈ -------- β€” manages instances of items - takes in an el and props - returns an item object --- - target = el - o = {object} = props - scrollEl = 'string' | object - verticalPosition = number - off = boolean - parentClass = 'string' - stickyClass = 'string' - stuckClass = 'string' --- - defined later - parent = dom element - state = 'string' - offset = number - stickyStart = number - stickyStop = number - returns an instance object */ ; _proto.addInstance = function addInstance(el, props) { var _this2 = this; var item = { el: el, parent: el.parentNode, props: props }; if (props.positionVal === 'fixed' || props.useStickyClasses) { this.isWin = this.props.scrollEl === window; var se = this.isWin ? window : this.getClosestParent(item.el, item.props.scrollEl); this.computeScrollOffsets(item); this.toggleClasses(item.parent, '', props.parentClass); item.state = 'default'; item.stateChange = 'default'; item.stateContainer = function () { return _this2.manageState(item); }; se.addEventListener('scroll', item.stateContainer); } return item; } /* -------- getParent πŸ‘¨β€ -------- - a helper function that gets the target element's parent selected el - only used for non `window` scroll elements - supports older browsers */ ; _proto.getClosestParent = function getClosestParent(el, match) { // p = parent element var p = match; var e = el; if (e.parentElement === p) return p; // traverse up the dom tree until we get to the parent while (e.parentElement !== p) { e = e.parentElement; } // return parent element return p; } /* -------- getTopPosition -------- - a helper function that gets the topPosition of a Stickybit element - from the top level of the DOM */ ; _proto.getTopPosition = function getTopPosition(el) { if (this.props.useGetBoundingClientRect) { return el.getBoundingClientRect().top + (this.props.scrollEl.pageYOffset || document.documentElement.scrollTop); } var topPosition = 0; do { topPosition = el.offsetTop + topPosition; } while (el = el.offsetParent); return topPosition; } /* computeScrollOffsets πŸ“Š --- computeScrollOffsets for Stickybits - defines - offset - start - stop */ ; _proto.computeScrollOffsets = function computeScrollOffsets(item) { var it = item; var p = it.props; var el = it.el; var parent = it.parent; var isCustom = !this.isWin && p.positionVal === 'fixed'; var isTop = p.verticalPosition !== 'bottom'; var scrollElOffset = isCustom ? this.getTopPosition(p.scrollEl) : 0; var stickyStart = isCustom ? this.getTopPosition(parent) - scrollElOffset : this.getTopPosition(parent); var stickyChangeOffset = p.customStickyChangeNumber !== null ? p.customStickyChangeNumber : el.offsetHeight; var parentBottom = stickyStart + parent.offsetHeight; it.offset = !isCustom ? scrollElOffset + p.stickyBitStickyOffset : 0; it.stickyStart = isTop ? stickyStart - it.offset : 0; it.stickyChange = it.stickyStart + stickyChangeOffset; it.stickyStop = isTop ? parentBottom - (el.offsetHeight + it.offset) : parentBottom - window.innerHeight; } /* toggleClasses βš–οΈ --- toggles classes (for older browser support) r = removed class a = added class */ ; _proto.toggleClasses = function toggleClasses(el, r, a) { var e = el; var cArray = e.className.split(' '); if (a && cArray.indexOf(a) === -1) cArray.push(a); var rItem = cArray.indexOf(r); if (rItem !== -1) cArray.splice(rItem, 1); e.className = cArray.join(' '); } /* manageState πŸ“ --- - defines the state - normal - sticky - stuck */ ; _proto.manageState = function manageState(item) { var _this3 = this; // cache object var it = item; var p = it.props; var state = it.state; var stateChange = it.stateChange; var start = it.stickyStart; var change = it.stickyChange; var stop = it.stickyStop; // cache props var pv = p.positionVal; var se = p.scrollEl; var sticky = p.stickyClass; var stickyChange = p.stickyChangeClass; var stuck = p.stuckClass; var vp = p.verticalPosition; var isTop = vp !== 'bottom'; var aS = p.applyStyle; var ns = p.noStyles; /* requestAnimationFrame --- - use rAF - or stub rAF */ var rAFStub = function rAFDummy(f) { f(); }; var rAF = !this.isWin ? rAFStub : window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || rAFStub; /* define scroll vars --- - scroll - notSticky - isSticky - isStuck */ var scroll = this.isWin ? window.scrollY || window.pageYOffset : se.scrollTop; var notSticky = scroll > start && scroll < stop && (state === 'default' || state === 'stuck'); var isSticky = isTop && scroll <= start && (state === 'sticky' || state === 'stuck'); var isStuck = scroll >= stop && state === 'sticky'; /* Unnamed arrow functions within this block --- - help wanted or discussion - view test.stickybits.js - `stickybits .manageState `position: fixed` interface` for more awareness πŸ‘€ */ if (notSticky) { it.state = 'sticky'; } else if (isSticky) { it.state = 'default'; } else if (isStuck) { it.state = 'stuck'; } var isStickyChange = scroll >= change && scroll <= stop; var isNotStickyChange = scroll < change / 2 || scroll > stop; if (isNotStickyChange) { it.stateChange = 'default'; } else if (isStickyChange) { it.stateChange = 'sticky'; } // Only apply new styles if the state has changed if (state === it.state && stateChange === it.stateChange) return; rAF(function () { var _styles2, _classes, _styles3, _extends2, _classes2, _style$classes; var stateStyles = { sticky: { styles: (_styles2 = { position: pv, top: '', bottom: '' }, _styles2[vp] = p.stickyBitStickyOffset + "px", _styles2), classes: (_classes = {}, _classes[sticky] = true, _classes) }, default: { styles: (_styles3 = {}, _styles3[vp] = '', _styles3), classes: {} }, stuck: { styles: _extends((_extends2 = {}, _extends2[vp] = '', _extends2), pv === 'fixed' && !ns || !_this3.isWin ? { position: 'absolute', top: '', bottom: '0' } : {}), classes: (_classes2 = {}, _classes2[stuck] = true, _classes2) } }; if (pv === 'fixed') { stateStyles.default.styles.position = ''; } var style = stateStyles[it.state]; style.classes = (_style$classes = {}, _style$classes[stuck] = !!style.classes[stuck], _style$classes[sticky] = !!style.classes[sticky], _style$classes[stickyChange] = isStickyChange, _style$classes); aS(style, item); }); } /* applyStyle --- - apply the given styles and classes to the element */ ; _proto.applyStyle = function applyStyle(_ref, item) { var styles = _ref.styles, classes = _ref.classes; // cache object var it = item; var e = it.el; var p = it.props; var stl = e.style; // cache props var ns = p.noStyles; var cArray = e.className.split(' '); // Disable due to bug with old versions of eslint-scope and for ... in // https://github.com/eslint/eslint/issues/12117 // eslint-disable-next-line no-unused-vars for (var cls in classes) { var addClass = classes[cls]; if (addClass) { if (cArray.indexOf(cls) === -1) cArray.push(cls); } else { var idx = cArray.indexOf(cls); if (idx !== -1) cArray.splice(idx, 1); } } e.className = cArray.join(' '); if (styles['position']) { stl['position'] = styles['position']; } if (ns) return; // eslint-disable-next-line no-unused-vars for (var key in styles) { stl[key] = styles[key]; } }; _proto.update = function update(updatedProps) { var _this4 = this; if (updatedProps === void 0) { updatedProps = null; } this.instances.forEach(function (instance) { _this4.computeScrollOffsets(instance); if (updatedProps) { // eslint-disable-next-line no-unused-vars for (var updatedProp in updatedProps) { instance.props[updatedProp] = updatedProps[updatedProp]; } } }); return this; } /* removes an instance πŸ‘‹ -------- - cleanup instance */ ; _proto.removeInstance = function removeInstance(instance) { var _styles4, _classes3; var e = instance.el; var p = instance.props; this.applyStyle({ styles: (_styles4 = { position: '' }, _styles4[p.verticalPosition] = '', _styles4), classes: (_classes3 = {}, _classes3[p.stickyClass] = '', _classes3[p.stuckClass] = '', _classes3) }, instance); this.toggleClasses(e.parentNode, p.parentClass); } /* cleanup πŸ› -------- - cleans up each instance - clears instance */ ; _proto.cleanup = function cleanup() { for (var i = 0; i < this.instances.length; i += 1) { var instance = this.instances[i]; if (instance.stateContainer) { instance.props.scrollEl.removeEventListener('scroll', instance.stateContainer); } this.removeInstance(instance); } this.manageState = false; this.instances = []; }; return Stickybits; }(); /* export -------- exports StickBits to be used 🏁 */ function stickybits(target, o) { return new Stickybits(target, o); } return stickybits; })));