PageSourceSearch

https://depechecode.io/wp-content/plugins/oxygen/plugin/animations…/js/parallax.js?bd_ver=6.2.0-beta.9

js depechecode.io collected 2026-10-04 14:51:09 UTC 7,779 bytes, 285 lines download raw bytes

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

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.