1/* global gsap, ScrollTrigger, imagesLoaded */ 2(function () { 3 const { matchMedia, debounce, mergeObjects } = BreakdanceFrontend.utils; 4 class BreakdanceSticky { 5 defaultOptions = { 6 position: "none", 7 offset: 0, 8 disable_at: null, 9 relative_to_viewport: false, 10 }; 11 12 stickyInitialized = false; 13 animatingClass = "is-animating"; 14 15 constructor(selector, options) { 16 gsap.registerPlugin(ScrollTrigger); 17 18 this.update = debounce(this._update, 100); 19 20 this.selector = selector; 21 this.options = mergeObjects(this.defaultOptions, options); 22 this.rootEl = document.documentElement; 23 24 this.init(); 25 } 26 27 getOffset() { 28 if (!this.options.offset) return 0; 29 30 const { BASE_BREAKPOINT_ID, breakpoints } = 31 window.BreakdanceFrontend.data; 32 33 const foundBreakpoint = breakpoints 34 .filter((b) => b.id !== BASE_BREAKPOINT_ID) 35 .sort((a, b) => a.maxWidth - b.maxWidth) 36 .find((b) => matchMedia(b.id)); 37 38 const currentBreakpoint = foundBreakpoint?.id || BASE_BREAKPOINT_ID; 39 40 // version >= 1.1 - backwards compatibility for non-breakpoint values. 41 const offset = 42 this.options.offset[currentBreakpoint]?.number || 43 this.options.offset.number || 44 0; 45 46 return Math.max(offset, 0); 47 } 48 49 canSticky() { 50 const { BASE_BREAKPOINT_ID } = window.BreakdanceFrontend.data; 51 const breakpoint = this.options.disable_at; 52 53 if (!breakpoint) return true; 54 if (breakpoint === BASE_BREAKPOINT_ID) return false; 55 56 return !matchMedia(breakpoint); 57 } 58 59 getTrigger(relativeTo = "parent", selector) { 60 if (relativeTo === "viewport") { 61 return document.body; 62 } else if (relativeTo === "custom" && selector) { 63 try { 64 return document.querySelector(selector); 65 } catch (e) {} 66 } 67 68 return ( 69 this.element.closest(".bde-column") || 70 this.element.closest(".section-container") || 71 document.body 72 ); 73 } 74 75 getStickyOffset(options) { 76 const offset = this.getOffset(); 77 const adminBar = document.querySelector("#wpadminbar"); 78 79 // Accommodate logged-in users, don't let the element get hidden behind the WP Admin Bar. 80 if (adminBar && options.position === "top") { 81 const barHeight = adminBar.getBoundingClientRect().height; 82 if (offset < barHeight) { 83 return offset + barHeight; 84 } 85 } 86 87 return offset; 88 } 89 90 createSticky() { 91 if (this.stickyInitialized) return; 92 this.stickyInitialized = true; 93 94 const { debug, position, relative_to, relative_selector } = this.options; 95 96 if (position === "none") return; 97 98 const offset = this.getStickyOffset(this.options); 99 const height = this.element.offsetHeight; 100 101 const keywords = { 102 top: `top+=${height}px`, 103 center: `center+=${height / 2}px`, 104 bottom: `bottom`, 105 }; 106 107 const start = `${position}-=${offset} ${position}`; 108 const end = `bottom-=${offset} ${keywords[position]}`; 109 const endTrigger = this.getTrigger(relative_to, relative_selector); 110 let delayedCall; 111 112 const computedStyle = getComputedStyle(this.element); 113 114 if (computedStyle.zIndex === "auto") { 115 gsap.set(this.element, { 116 zIndex: "var(--bde-z-index-sticky)", 117 }); 118 } 119 120 this.stickyTween = gsap.to(this.element, { 121 scrollTrigger: { 122 trigger: this.element, 123 pin: this.element, 124 start, 125 end, 126 endTrigger, 127 markers: debug, 128 toggleClass: "is-sticky", 129 pinSpacing: false, 130 onUpdate: (self) => { 131 if (self.getVelocity() === 0) return; 132 this.element.classList.add(this.animatingClass); 133 134 if (delayedCall) delayedCall.kill(); 135 delayedCall = gsap.delayedCall(0.3, () => { 136 this.element.classList.remove(this.animatingClass); 137 }); 138 }, 139 }, 140 ease: "linear", 141 }); 142 } 143 144 _update(options) { 145 this.options = mergeObjects(this.defaultOptions, options); 146 this.destroy(); 147 this.init(); 148 } 149 150 destroyResizeObserver() { 151 if (!this.resizeObserver) return; 152 this.resizeObserver.unobserve(document.body); 153 } 154 155 destroy() { 156 this.stickyInitialized = false;
157 if (!this.stickyTween) return; 158 this.stickyTween.kill(true); 159 this.stickyTween.scrollTrigger?.kill(true); 160 this.stickyTween = null; 161 } 162 163 remove() { 164 this.destroyResizeObserver(); 165 this.destroy(); 166 } 167 168 onResize(callback) { 169 this.resizeObserver = new ResizeObserver(callback); 170 this.resizeObserver.observe(document.body); 171 172 return () => { 173 this.resizeObserver.disconnect(); 174 }; 175 } 176 177 refresh() { 178 ScrollTrigger.refresh(); 179 } 180 181 initOrDestroy() { 182 if (this.canSticky()) { 183 this.createSticky(); 184 } else { 185 this.destroy(); 186 } 187 } 188 189 init() { 190 this.element = document.querySelector(this.selector); 191 this.parent = this.element.closest(".section-container"); 192 193 this.onResize(() => this.initOrDestroy()); 194 } 195 196 static dontLetScrollTriggerMutateScrollPosition() { 197 window.addEventListener("load", () => { 198 if (!location.hash) return; 199 if (window.bdeAnimationScrolled) return; 200 const scrollElem = document.querySelector(location.hash); 201 if (!scrollElem) return; 202 203 // Safari fix: Scroll to the element after the next repaint. 204 requestAnimationFrame(() => { 205 scrollElem.scrollIntoView({ 206 behavior: "smooth", 207 }); 208 }); 209 210 // Prevent scrolling to the same element twice. 211 window.bdeAnimationScrolled = true; 212 }); 213 } 214 } 215 216 window.BreakdanceSticky = BreakdanceSticky; 217 BreakdanceSticky.dontLetScrollTriggerMutateScrollPosition(); 218})();
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.