PageSourceSearch

https://saintaignanlejaillard.fr/wp-content/plugins/unlimited-elem…/js/extension/sticky-section.js?ver=1.3.4

js saintaignanlejaillard.fr collected 2026-10-02 23:51:56 UTC 11,758 bytes, 325 lines download raw bytes

1/**
2 * Handles all sticky logic on the frontend:
3 *  - Sticky Position : top | bottom | column
4 *  - Sticky Until    : stop at a CSS ID
5 *  - Sticky Offset   : px gap from viewport edge
6 *  - Background/Shadow change when sticky activates
7 *  - Device Control  : skip sticky on specific breakpoints
8 *  - Entrance Animation: fade | slide-down | slide-up
9 */
10(function ($) {
11  "use strict";
12
13  // ─────────────────────────────────────────────────────────────────────────
14  // BREAKPOINT DETECTION
15  // Matches Elementor's default breakpoints
16  // ─────────────────────────────────────────────────────────────────────────
17  var getDevice = function () {
18    var w = window.innerWidth;
19    if (w <= 767) return "mobile";
20    if (w <= 1024) return "tablet";
21    return "desktop";
22  };
23
24  // ─────────────────────────────────────────────────────────────────────────
25  // ENTRANCE ANIMATION
26  // Called once when the element becomes sticky for the first time
27  // ─────────────────────────────────────────────────────────────────────────
28  var playEntranceAnimation = function ($el, type, duration) {
29    if (!type || type === "none") return;
30
31    $el.css({ "animation-duration": duration + "ms" });
32    $el.addClass("pea-sticky-anim-" + type);
33
34    // Remove class after animation ends so it can replay next time
35    setTimeout(function () {
36      $el.removeClass("pea-sticky-anim-" + type);
37    }, duration + 50);
38  };
39
40  // ─────────────────────────────────────────────────────────────────────────
41  // SINGLE ELEMENT STICKY HANDLER
42  // ─────────────────────────────────────────────────────────────────────────
43  var PeaStickyHandler = function ($el) {
44    var raw = $el.attr("data-pea-sticky");
45    if (!raw) return;
46
47    var cfg;
48    try {
49      cfg = JSON.parse(raw);
50    } catch (e) {
51      return;
52    }
53
54    if (!cfg || !cfg.enabled) return;
55
56    // Device check — bail if sticky is disabled for this breakpoint
57    var device = getDevice();
58    if (device === "desktop" && !cfg.desktop) return;
59    if (device === "tablet" && !cfg.tablet) return;
60    if (device === "mobile" && !cfg.mobile) return;
61
62    var position = cfg.position || "top";
63    var offset = parseInt(cfg.offset, 10) || 0;
64    var untilSel = cfg.until || "";
65    var animation = cfg.animation || "none";
66    var animDur = parseInt(cfg.animDuration, 10) || 400;
67
68    var bgEnable = !!cfg.bgEnable;
69    var bgColor = cfg.bgColor || "rgba(255,255,255,1)";
70    var transition = parseInt(cfg.transition, 10) || 300;
71    var shadowEnable = !!cfg.shadowEnable;
72    var shadowColor = cfg.shadowColor || "rgba(0,0,0,0.12)";
73
74    // ── COLUMN MODE ───────────────────────────────────────────────────────
75    // Sticky within its parent container rather than the viewport.
76    // Uses position:sticky on the element itself.
77    if (position === "column") {
78      $el.css({
79        position: "sticky",
80        top: offset + "px",
81        "z-index": 99,
82      });
83
84      // Apply bg/shadow as permanent style for column mode
85      if (bgEnable) {
86        $el.css({
87          "background-color": bgColor,
88          transition:
89            "background-color " +
90            transition +
91            "ms ease, box-shadow " +
92            transition +
93            "ms ease",
94        });
95        if (shadowEnable) {
96          $el.css("box-shadow", "0 4px 24px " + shadowColor);
97        }
98      }
99      return; // Column mode requires no scroll listener
100    }
101
102    // ── TOP / BOTTOM MODE ─────────────────────────────────────────────────
103    var $placeholder = null; // Reserve space when element is lifted out of flow
104    var isSticky = false;
105    var originalStyle = {}; // Store original inline styles for restore
106
107    // Snapshot of the element's natural layout position
108    var elNaturalTop = 0;
109    var elNaturalBottom = 0;
110    var elHeight = 0;
111    var elWidth = 0;
112
113    // Read natural position once before any scroll
114    var cacheNaturalMetrics = function () {
115      // Temporarily detach sticky so measurements are natural
116      $el.removeClass("pea-is-sticky");
117      if ($placeholder) {
118        $placeholder.css("height", 0);
119      }
120
121      var rect = $el[0].getBoundingClientRect();
122      var scrollY = window.pageYOffset;
123      elNaturalTop = rect.top + scrollY;
124      elNaturalBottom = rect.bottom + scrollY;
125      elHeight = $el.outerHeight(true);
126      elWidth = $el.outerWidth();
127    };
128
129    // Create a transparent placeholder div to hold the space
130    var ensurePlaceholder = function () {
131      if (!$placeholder) {
132        $placeholder = $(
133          '<div class="pea-sticky-placeholder" aria-hidden="true"></div>',
134        );
135        $el.after($placeholder);
136      }
137    };
138
139    var makeSticky = function () {
140      if (isSticky) return;
141      isSticky = true;
142
143      ensurePlaceholder();
144      $placeholder.css({
145        height: elHeight + "px",
146        width: elWidth + "px",
147        display: "block",
148      });
149
150      $el.addClass("pea-is-sticky");
151
152      var stickyCSS = {
153        position: "fixed",
154        "z-index": 9999,
155        width: elWidth + "px",
156        left: $placeholder.offset().left + "px",
157      };
158
159      if (position === "top") {
160        stickyCSS.top = offset + "px";
161        stickyCSS.bottom = "auto";
162      } else {
163        stickyCSS.bottom = offset + "px";
164        stickyCSS.top = "auto";
165      }
166
167      // Background change
168      if (bgEnable) {
169        stickyCSS["background-color"] = bgColor;
170        stickyCSS["transition"] =
171          "background-color " +
172          transition +
173          "ms ease, box-shadow " +
174          transition +
175          "ms ease";
176        if (shadowEnable) {
177          stickyCSS["box-shadow"] = "0 4px 24px " + shadowColor;
178        }
179      }
180
181      $el.css(stickyCSS);
182
183      // Entrance animation (play once per sticky activation)
184      playEntranceAnimation($el, animation, animDur);
185    };
186
187    var makeUnSticky = function () {
188      if (!isSticky) return;
189      isSticky = false;
190
191      $el.removeClass("pea-is-sticky");
192      $el.css({
193        position: "",
194        top: "",
195        bottom: "",
196        left: "",
197        width: "",
198        "z-index": "",
199        "background-color": "",
200        "box-shadow": "",
201        transition: "",
202      });
203
204      if ($placeholder) {
205        $placeholder.css({ height: 0, display: "none" });
206      }
207    };
208
209    // ── SCROLL HANDLER ────────────────────────────────────────────────────
210    var onScroll = function () {
211      var scrollY = window.pageYOffset;
212      var vpHeight = window.innerHeight;
213
214      // ── "Sticky Until" stop logic ──
215      var shouldStop = false;
216
217      if (untilSel) {
218          var $stopEl = $(untilSel);
219
220          if ($stopEl.length && position === "top") {
221              var stopTop = $stopEl.offset().top;
222
223              shouldStop = scrollY + offset + elHeight >= stopTop;
224          }
225      }
226
227      // ── Top position ──
228      if (position === "top") {
229        if (scrollY + offset >= elNaturalTop && !shouldStop) {
230            makeSticky();
231        } else {
232            makeUnSticky();
233        }
234        return;
235      }
236
237      // ── Bottom position ──
238      if (position === "bottom") {
239        // Stick when the element's natural bottom is scrolled out of viewport
240        if (scrollY + vpHeight <= elNaturalBottom) {
241          makeSticky();
242        } else {
243          makeUnSticky();
244        }
245      }
246    };
247
248    // ── RESIZE HANDLER ────────────────────────────────────────────────────
249    var onResize = function () {
250      makeUnSticky();
251      cacheNaturalMetrics();
252
253      // Re-check device
254      var newDevice = getDevice();
255      if (newDevice === "desktop" && !cfg.desktop) return;
256      if (newDevice === "tablet" && !cfg.tablet) return;
257      if (newDevice === "mobile" && !cfg.mobile) return;
258
259      onScroll();
260    };
261
262    // ── INIT ─────────────────────────────────────────────────────────────
263    cacheNaturalMetrics();
264
265    // Use passive listeners for performance
266    window.addEventListener("scroll", onScroll, { passive: true });
267    window.addEventListener("resize", debounce(onResize, 150), {
268      passive: true,
269    });
270
271    // Run once on init in case page loads already scrolled
272    onScroll();
273  };
274
275  // ─────────────────────────────────────────────────────────────────────────
276  // DEBOUNCE UTILITY
277  // ─────────────────────────────────────────────────────────────────────────
278  var debounce = function (fn, delay) {
279    var timer;
280    return function () {
281      clearTimeout(timer);
282      timer = setTimeout(fn, delay);
283    };
284  };
285
286  // ─────────────────────────────────────────────────────────────────────────
287  // ELEMENTOR FRONTEND INIT
288  // Init sticky on sections, columns, and containers
289  // ─────────────────────────────────────────────────────────────────────────
290  var initAllStickyElements = function ($scope) {
291    // The scope itself might be a sticky element
292    if ($scope.is("[data-pea-sticky]")) {
293      PeaStickyHandler($scope);
294    }
295    // Or it contains sticky elements
296    $scope.find("[data-pea-sticky]").each(function () {
297      PeaStickyHandler($(this));
298    });
299  };
300
301  $(window).on("elementor/frontend/init", function () {
302    // Section
303    elementorFrontend.hooks.addAction(
304      "frontend/element_ready/section",
305      initAllStickyElements,
306    );
307    // Column
308    elementorFrontend.hooks.addAction(
309      "frontend/element_ready/column",
310      initAllStickyElements,
311    );
312    // Container (Flexbox / Grid)
313    elementorFrontend.hooks.addAction(
314      "frontend/element_ready/container",
315      initAllStickyElements,
316    );
317  });
318
319  // Also init on DOMContentLoaded for non-widget page loads
320  $(document).ready(function () {
321    $("[data-pea-sticky]").each(function () {
322      PeaStickyHandler($(this));
323    });
324  });
325})(jQuery);

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.