1/* global gsap, ScrollTrigger, imagesLoaded */ 2(function () { 3 const { 4 is, 5 matchMedia, 6 toArray, 7 debounce, 8 onResize, 9 mergeObjects, 10 prefersReducedMotion 11 } = BreakdanceFrontend.utils; 12 13 class BreakdanceParallax { 14 tweens = []; 15 enabledClass = 'breakdance-animation-enabled'; 16 animatingClass = 'is-animating'; 17 18 defaultOptions = { 19 enabled: false, 20 opacity: null, 21 rotation: null, 22 scale: null, 23 x: null, 24 y: null, 25 blur: null, 26 background_color: null, 27 advanced: { 28 ease: 'linear', 29 disable_at: null, 30 relative_to: 'viewport', 31 origin: null, 32 debug: false 33 } 34 } 35 36 initialized = false; 37 38 constructor(selector, options) { 39 gsap.registerPlugin(ScrollTrigger); 40 41 this.createTween = this.createTween.bind(this); 42 this.update = debounce(this._update, 100); 43 44 this.selector = selector; 45 this.options = mergeObjects(this.defaultOptions, options); 46 this.rootEl = document.documentElement; 47 48 this.init(); 49 } 50 51 parseValue(value) { 52 if (!value) return value; 53 if (typeof value === 'string' && value.startsWith('var(')) { 54 // CSS variable 55 const cssVar = value.replace(/var\(([^)]+)\)/, '$1'); 56 return getComputedStyle(this.element).getPropertyValue(cssVar); 57 } 58 // Unit value is an object. 59 return typeof value === 'object' ? value.style : value; 60 } 61 62 parseAnchors(trigger) { 63 const [vBottom, vTop] = trigger || [0, 100]; 64 65 return { 66 bottom: 100 - vBottom, 67 top: 100 - vTop 68 } 69 } 70 71 getTrigger(relativeTo = 'viewport', selector) { 72 if (relativeTo === 'page') { 73 return document.documentElement; 74 } else if (relativeTo === 'custom' && selector) { 75 try { 76 return document.querySelector(selector); 77 } catch (e) {} 78 } 79 80 return this.element; 81 } 82 83 getScrollTriggerObject(values) { 84 const { bottom, top } = this.parseAnchors(values.trigger); 85 const { debug, scrub, relative_to, relative_selector } = this.options.advanced; 86 const speed = this.getDuration(scrub) || true; 87 let delayedCall; 88 89 const trigger = this.getTrigger(relative_to, relative_selector); 90 91 return { 92 trigger: trigger, 93 start: `top ${bottom}%`, // Element x Viewport 94 end: `bottom ${top}%`, // Element x Viewport 95 scrub: speed, 96 markers: debug, 97 toggleClass: 'is-parallax-active', 98 onUpdate: (self) => { 99 if (self.getVelocity() === 0) return; 100 this.element.classList.add(this.animatingClass); 101 102 if (delayedCall) delayedCall.kill(); 103 delayedCall = gsap.delayedCall(0.3, () => { 104 this.element.classList.remove(this.animatingClass); 105 }); 106 } 107 }; 108 } 109 110 createTween([prop, values]) { 111 const cssProp = prop.replace(/_/g, '-'); 112 const ease = this.options.advanced.ease; 113 const scrollTrigger = this.getScrollTriggerObject(values); 114 const startValue = this.parseValue(values.start); 115 const middleValue = this.parseValue(values.middle); 116 const endValue = this.parseValue(values.end); 117 118 const from = { [cssProp]: startValue, ease }; 119 const middle = { [cssProp]: middleValue, ease }; 120 const to = { [cssProp]: endValue, ease }; 121 122 const tl = gsap.timeline({ scrollTrigger }); 123 124 const startEndSteps = !is.nil(startValue) && !is.nil(endValue); 125 const allSteps = startEndSteps && !is.nil(middleValue); 126 127 if (allSteps) { 128 // Start -> Middle -> End 129 tl.fromTo(this.element, from, middle); 130 tl.to(this.element, to); 131 } else if (startEndSteps) { 132 // Start -> End 133 tl.fromTo(this.element, from, to); 134 } else if (!is.nil(startValue)) { 135 // Start -> Original Value 136 tl.from(this.element, from); 137 } else { 138 // Original Value -> End 139 tl.to(this.element, to); 140 } 141 142 return tl; 143 } 144 145 canAnimate() { 146 const breakpoint = this.options.advanced.disable_at; 147 if (!breakpoint) return true; 148 return !matchMedia(breakpoint); 149 } 150 151 getDuration(value) { 152 if (!value) return value; 153 if (value.unit === 's') return value.number; 154 return value.number / 1000; // Convert MS to S 155 } 156 157 initTweens() { 158 if (this.initialized) return; 159 this.initialized = true; 160 161 const { origin } = this.options.advanced; 162 163 if (origin) { 164 gsap.set(this.element, { 165 transformOrigin: `${origin.x}% ${origin.y}%` 166 }); 167 } 168 169 this.tweens = Object.entries(this.options) 170 .filter(([, obj]) => !is.nil(obj?.start) || !is.nil(obj?.end)) 171 .map(this.createTween); 172 } 173 174 removeDebugMarkers() { 175 // Remove ScrollTrigger markers 176 const markers = [ 177 'start', 178 'end', 179 'scroller-start', 180 'scroller-end' 181 ]; 182
183 markers.forEach((suffix) => { 184 const className = `.gsap-marker-${suffix}`; 185 toArray(className).forEach((elem) => elem.remove()); 186 }); 187 } 188 189 _update(options) { 190 this.options = mergeObjects(this.defaultOptions, options); 191 this.destroy(); 192 this.init(); 193 } 194 195 destroyTweens() { 196 if (!this.element) return; 197 if (!this.tweens) return; 198 199 this.tweens.forEach((tween) => { 200 // Killing the tween should automatically kill scrollTrigger, 201 // but that's always not the case for some reason. 202 tween.kill(); 203 tween.scrollTrigger?.kill(); 204 }); 205 206 this.tweens = []; 207 208 // Remove all inline styles 209 gsap.set(this.element, { 210 clearProps: 'all' 211 }); 212 213 this.initialized = false; 214 } 215 216 refresh() { 217 ScrollTrigger.refresh(true); 218 } 219 220 destroy() { 221 this.destroyTweens(); 222 this.removeDebugMarkers(); 223 } 224 225 initOrDestroy() { 226 if (this.canAnimate()) { 227 this.initTweens(); 228 } else { 229 this.destroyTweens(); 230 } 231 } 232 233 init() { 234 if (!this.options.enabled) return; 235 if (prefersReducedMotion()) return; 236 237 this.element = document.querySelector(this.selector); 238 239 onResize(() => this.initOrDestroy()); 240 241 this.rootEl.classList.add(this.enabledClass); 242 } 243 244 static autoload() { 245 const loaded = imagesLoaded(document.body); 246 247 loaded.on('always', () => { 248 // Refresh all instances. 249 const event = new Event("breakdance_refresh_animations", { bubbles: true }); 250 document.dispatchEvent(event); 251 252 // Refresh ScrollTrigger only once. 253 ScrollTrigger.refresh(true); 254 }); 255 } 256 } 257 258 window.BreakdanceParallax = BreakdanceParallax; 259 260 // Autoload 261 BreakdanceParallax.autoload(); 262}());
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.