PageSourceSearch

https://www.alkami.com/wp-content/plugins/breakdance/plugin/animations/scrolling/js/parallax.js?bd_ver=2.8.1

js alkami.com collected 2026-09-24 09:02:06 UTC 6,783 bytes, 262 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  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.