PageSourceSearch

https://henleyroofing.net/wp-content/plugins/breakdance/plugin/animations/sticky/js/sticky.js?bd_ver=2.8.3

js henleyroofing.net collected 2026-09-26 16:24:25 UTC 5,942 bytes, 218 lines download raw bytes

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.