1/** 2 * JazzyStuck â detect when position:sticky elements stick 3 * 4 * Adds a class to a `position: sticky` element when it's pinned to its 5 * `top` offset. Use for shadows, borders, background changes, or tighter 6 * padding on stuck headers/subnavs. 7 * 8 * Markup pattern: 9 * <header data-jazzy-stuck>â¦</header> 10 * 11 * Required CSS (library does not supply this): 12 * header { position: sticky; top: 0; } 13 * header.stuck { box-shadow: 0 2px 8px rgba(0,0,0,.1); } 14 * 15 * Options: 16 * data-jazzy-stuck-class="is-pinned" Class toggled when stuck (default: "stuck") 17 * 18 * Programmatic: 19 * new JazzyStuck('.subnav') // selector â observes all matches 20 * new JazzyStuck(element) // single element 21 * JazzyStuck.init(element, { stuckClass: 'pinned' }) 22 * 23 * Events emitted on the element: 24 * jazzystuck:change { detail: { stuck: boolean } } 25 * 26 * How it works: 27 * Uses IntersectionObserver with a negative rootMargin equal to the 28 * element's CSS `top` value. Fires when the element crosses that 29 * threshold. The callback reads boundingClientRect.y to decide whether 30 * the element is currently pinned vs. scrolling. 31 * 32 * Known limitations: 33 * - Top-sticky only. No detection for bottom/left/right sticky. 34 * - `top` value is read once at init. Responsive CSS that changes `top` 35 * at a breakpoint won't re-compute â destroy and re-init if needed. 36 * - Doesn't detect "unstick at parent bottom" (when sticky containing 37 * block scrolls past and the element scrolls with it). The class 38 * stays applied. Not usually visible since the element is offscreen. 39 * 40 * @since 1.0.0 41 */ 42(function () { 43 'use strict'; 44 45 if (!('IntersectionObserver' in window)) return; 46 47 function JazzyStuck(target, opts) { 48 opts = opts || {}; 49 this.stuckClass = opts.stuckClass || 'stuck'; 50 this.elements = []; 51 this._observers = []; 52 53 var els; 54 if (target instanceof Element) { 55 els = [target]; 56 } else if (typeof target === 'string') { 57 els = Array.from(document.querySelectorAll(target)); 58 } else if (target && typeof target.length === 'number') { 59 els = Array.from(target); 60 } else { 61 return; 62 } 63 64 var self = this;
65 els.forEach(function (el) { self._observe(el); }); 66 } 67 68 JazzyStuck.prototype._getTopOffset = function (el) { 69 var top = window.getComputedStyle(el).getPropertyValue('top'); 70 if (!top) return null; 71 var parsed = parseInt(top, 10); 72 return isNaN(parsed) ? null : parsed + 1; 73 }; 74 75 JazzyStuck.prototype._observe = function (el) { 76 var self = this; 77 var topOffset = this._getTopOffset(el); 78 79 // If `top` isn't set, the element isn't actually sticky â skip. 80 if (topOffset === null) return; 81 82 var observer = new IntersectionObserver(function (entries) { 83 // A scroll lock pins the body at a negative top, which moves every 84 // observed element and fires this â but the page hasn't scrolled, so 85 // the stuck state must hold or the header sheds its skin on modal open. 86 if (document.documentElement.classList.contains('dialog-open')) return; 87 88 entries.forEach(function (entry) { 89 var stuck = entry.boundingClientRect.y < topOffset; 90 var wasStuck = entry.target.classList.contains(self.stuckClass); 91 92 entry.target.classList.toggle(self.stuckClass, stuck); 93 94 if (stuck !== wasStuck) { 95 entry.target.dispatchEvent(new CustomEvent('jazzystuck:change', { 96 detail: { stuck: stuck } 97 })); 98 } 99 }); 100 }, { 101 root: null, 102 rootMargin: '-' + topOffset + 'px 0px 0px 0px', 103 threshold: [0, 1] 104 }); 105 106 observer.observe(el); 107 this.elements.push(el); 108 this._observers.push(observer); 109 el._jazzyStuck = this; 110 }; 111 112 JazzyStuck.prototype.destroy = function () { 113 this._observers.forEach(function (o) { o.disconnect(); }); 114 this._observers = []; 115 this.elements.forEach(function (el) { delete el._jazzyStuck; }); 116 this.elements = []; 117 }; 118 119 JazzyStuck.init = function (el, opts) { 120 if (el._jazzyStuck) return el._jazzyStuck; 121 return new JazzyStuck(el, opts); 122 }; 123 124 // ------------------------------------------------------------------ 125 // Auto-init from data attributes 126 // ------------------------------------------------------------------ 127 128 function autoInit() { 129 document.querySelectorAll('[data-jazzy-stuck]').forEach(function (el) { 130 if (el._jazzyStuck) return; 131 var opts = {}; 132 var stuckClass = el.getAttribute('data-jazzy-stuck-class'); 133 if (stuckClass) opts.stuckClass = stuckClass; 134 new JazzyStuck(el, opts); 135 }); 136 } 137 138 if (document.readyState === 'loading') { 139 document.addEventListener('DOMContentLoaded', autoInit); 140 } else { 141 autoInit(); 142 } 143 144 window.JazzyStuck = JazzyStuck; 145})();
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.