PageSourceSearch

https://www.indy.fr/wp-content/plugins/indy-elementor-widgets/widg…?ver=7.1.2-b-modified-1790778809

js indy.fr collected 2026-10-02 07:58:54 UTC 18,793 bytes, 591 lines download raw bytes

1(function ($) {
2  "use strict";
3
4  let indyAcc4InstanceSeq = 0;
5
6  // Fonction pour initialiser sur desktop (>= 1024px)
7  function initAccompagnement4WidgetDesktop() {
8    if (window.innerWidth < 1024) {
9      $(".indy-accompagnement-4-widget").each(function () {
10        this.style.removeProperty("--indy-acc4-fixed-img-sticky-top");
11      });
12      $(".indy-accompagnement-4-widget").removeClass("is-media-hidden");
13      return;
14    }
15    const $widget = $(".indy-accompagnement-4-widget");
16    if (!$widget.length) return;
17
18    // Éviter de dupliquer les listeners si Elementor ré-initialise le widget
19    if (!$widget.data("indyAcc4Id")) {
20      indyAcc4InstanceSeq += 1;
21      $widget.data("indyAcc4Id", indyAcc4InstanceSeq);
22    }
23    const instanceId = $widget.data("indyAcc4Id");
24    const scrollNs = `scroll.indyAcc4.${instanceId}`;
25    const resizeNs = `resize.indyAcc4.${instanceId}`;
26
27    const $mainWrapper = $widget.find(".main-wrapper");
28    const $fixedImgWrapper = $widget.find(".fixed-img-wrapper");
29    const $items = $widget.find(".content-wrapper .item");
30
31    if (!$mainWrapper.length || !$fixedImgWrapper.length || !$items.length) {
32      return;
33    }
34
35    function getMediaForItem($item) {
36      const svgUrl = $item.attr("data-anim");
37      if (svgUrl) {
38        return { type: "svg", url: svgUrl };
39      }
40
41      const explicitImg =
42        $item.attr("data-img") ||
43        $item.attr("data-image") ||
44        $item.attr("data-image-url");
45      if (explicitImg) {
46        return { type: "img", url: explicitImg };
47      }
48
49      // Fallback: prendre la première image "réelle" de l'item (pas le fallback de l'object)
50      const $img = $item
51        .find(".item-img img")
52        .filter(function () {
53          return !$(this).closest("object.svg-animation").length;
54        })
55        .first();
56
57      const imgUrl = $img.attr("src") || $img.attr("data-src");
58      if (!imgUrl) return null;
59
60      return { type: "img", url: imgUrl };
61    }
62
63    function ensureFixedSvgObject() {
64      let $obj = $fixedImgWrapper.find("object.svg-animation").first();
65      if ($obj.length) return $obj;
66
67      const obj = document.createElement("object");
68      obj.type = "image/svg+xml";
69      obj.setAttribute("data", "");
70      obj.setAttribute("aria-label", "Accompagnement");
71      obj.className = "svg-animation";
72      obj.style.display = "none";
73
74      const fallbackImg = document.createElement("img");
75      fallbackImg.src = "";
76      fallbackImg.alt = "Accompagnement";
77      fallbackImg.loading = "eager";
78      obj.appendChild(fallbackImg);
79
80      $fixedImgWrapper.append(obj);
81      $obj = $(obj);
82      return $obj;
83    }
84
85    function ensureFixedImgElement() {
86      let $img = $fixedImgWrapper.find("img.fixed-media").first();
87      if ($img.length) return $img;
88
89      const img = document.createElement("img");
90      img.className = "fixed-media";
91      img.alt = "Accompagnement";
92      img.loading = "eager";
93      img.style.display = "none";
94      $fixedImgWrapper.append(img);
95      $img = $(img);
96      return $img;
97    }
98
99    // Fonction pour changer le SVG
100    function changeSvg(svgUrl) {
101      const $currentSvgObject = ensureFixedSvgObject();
102      if (!$currentSvgObject.length) return;
103
104      const currentData = $currentSvgObject.attr("data");
105      if (currentData === svgUrl) return; // Déjà le bon SVG
106
107      // Vérifier si c'est le premier chargement (objet vide ou caché)
108      const isFirstLoad =
109        !currentData || currentData === "" || $currentSvgObject.is(":hidden");
110
111      // Créer un nouvel object avec l'élément DOM natif pour s'assurer que l'attribut data est correctement défini
112      // jQuery ne définit pas correctement l'attribut 'data' sur les éléments <object>
113      const newObject = document.createElement("object");
114      newObject.type = "image/svg+xml";
115      newObject.setAttribute("data", svgUrl); // Utiliser setAttribute pour l'attribut data
116      newObject.setAttribute("aria-label", "Accompagnement");
117      newObject.className = "svg-animation";
118
119      // Fallback img
120      const fallbackImg = document.createElement("img");
121      fallbackImg.src = svgUrl;
122      fallbackImg.alt = "Accompagnement";
123      fallbackImg.loading = "eager";
124      newObject.appendChild(fallbackImg);
125
126      // Écouter l'événement load pour s'assurer que le SVG est complètement chargé
127      newObject.addEventListener("load", function () {
128        // Afficher l'objet s'il s'agit du premier chargement
129        if (isFirstLoad) {
130          newObject.style.display = "";
131        }
132
133        // Forcer le redémarrage des animations en accédant au contenu SVG
134        try {
135          const svgDoc = newObject.contentDocument;
136          if (svgDoc) {
137            // Les animations SVG devraient redémarrer automatiquement
138            // Mais on peut forcer un reflow pour s'assurer
139            svgDoc.documentElement.offsetHeight;
140          }
141        } catch (e) {
142          // Ignorer les erreurs CORS si le SVG est sur un autre domaine
143        }
144      });
145
146      // Remplacer l'ancien object
147      $currentSvgObject.replaceWith(newObject);
148    }
149
150    function changeFixedImage(imgUrl) {
151      const $img = ensureFixedImgElement();
152      if (!$img.length) return;
153
154      const currentSrc = $img.attr("src");
155      if (currentSrc === imgUrl) return;
156
157      $img.attr("src", imgUrl);
158      $img.show();
159    }
160
161    function showSvgMode(svgUrl) {
162      const $img = $fixedImgWrapper.find("img.fixed-media");
163      if ($img.length) $img.hide();
164
165      const $obj = ensureFixedSvgObject();
166      if ($obj.length) $obj.show();
167      if (svgUrl) changeSvg(svgUrl);
168    }
169
170    function showImgMode(imgUrl) {
171      const $obj = $fixedImgWrapper.find("object.svg-animation");
172      if ($obj.length) $obj.hide();
173
174      changeFixedImage(imgUrl);
175    }
176
177    const fadeEndNs = `transitionend.indyAcc4Fade.${instanceId}`;
178
179    function parseCssDurationMs(raw, fallbackMs) {
180      if (!raw) return fallbackMs;
181      const value = raw.trim();
182      if (value.endsWith("ms"))
183        return Number(value.replace("ms", "")) || fallbackMs;
184      if (value.endsWith("s"))
185        return Math.round(
186          (Number(value.replace("s", "")) || fallbackMs / 1000) * 1000,
187        );
188      return Number(value) || fallbackMs;
189    }
190
191    function getFadeOutDurationMs() {
192      const el = $fixedImgWrapper[0];
193      if (!el) return 55;
194      const css = window.getComputedStyle(el);
195      const raw =
196        css.getPropertyValue("--indy-acc4-fade-out")?.trim() || "55ms";
197      return parseCssDurationMs(raw, 55);
198    }
199
200    let transitionTimeout = null;
201    let isTransitioning = false;
202    let pendingMedia = null;
203
204    function clearFadeOutSchedule() {
205      $fixedImgWrapper.off(fadeEndNs);
206      if (transitionTimeout) {
207        clearTimeout(transitionTimeout);
208        transitionTimeout = null;
209      }
210    }
211
212    function scheduleFadeOutEnd() {
213      const completeFadeOut = function () {
214        if (!isTransitioning) return;
215        clearFadeOutSchedule();
216        finishTransition();
217      };
218
219      const fadeOutMs = getFadeOutDurationMs();
220      $fixedImgWrapper.on(fadeEndNs, "object, img", function (e) {
221        if (e.propertyName !== "opacity") return;
222        completeFadeOut();
223      });
224      transitionTimeout = window.setTimeout(completeFadeOut, fadeOutMs + 40);
225    }
226
227    function applyFixedMedia(media) {
228      const nextKey = `${media.type}:${media.url}`;
229      if (media.type === "svg") showSvgMode(media.url);
230      else showImgMode(media.url);
231      $fixedImgWrapper.data("indyAcc4CurrentMediaKey", nextKey);
232    }
233
234    function finishTransition() {
235      clearFadeOutSchedule();
236
237      const target = pendingMedia;
238      pendingMedia = null;
239
240      if (target) applyFixedMedia(target);
241
242      window.requestAnimationFrame(function () {
243        $fixedImgWrapper.removeClass("is-fading");
244        isTransitioning = false;
245
246        // Nouvelle cible reçue pendant le fade-in : enchaîner
247        if (pendingMedia) {
248          const currentKey = $fixedImgWrapper.data("indyAcc4CurrentMediaKey");
249          const pendingKey = `${pendingMedia.type}:${pendingMedia.url}`;
250          if (currentKey !== pendingKey) transitionToMedia(pendingMedia);
251        }
252      });
253    }
254
255    function transitionToMedia(media) {
256      if (!media || !media.url) return;
257
258      const nextKey = `${media.type}:${media.url}`;
259      const currentKey = $fixedImgWrapper.data("indyAcc4CurrentMediaKey");
260
261      pendingMedia = media;
262
263      if (currentKey === nextKey) {
264        if ($fixedImgWrapper.hasClass("is-fading")) finishTransition();
265        else pendingMedia = null;
266        return;
267      }
268
269      // Transition déjà en cours : mettre à jour la cible, ne pas annuler le fade-out
270      if (isTransitioning) {
271        if (!transitionTimeout) scheduleFadeOutEnd();
272        return;
273      }
274
275      // Première fois: pas de fade-out (évite le flash)
276      if (!currentKey) {
277        applyFixedMedia(media);
278        pendingMedia = null;
279        return;
280      }
281
282      isTransitioning = true;
283      $fixedImgWrapper.addClass("is-fading");
284      scheduleFadeOutEnd();
285    }
286
287    function preloadDesktopMedia() {
288      const seen = new Set();
289      $items.each(function () {
290        const media = getMediaForItem($(this));
291        if (
292          !media ||
293          media.type !== "img" ||
294          !media.url ||
295          seen.has(media.url)
296        ) {
297          return;
298        }
299        seen.add(media.url);
300        const img = new Image();
301        img.decoding = "async";
302        img.src = media.url;
303      });
304    }
305
306    function getVisibleFixedMediaBottom(fixedImgWrapper) {
307      let mediaBottom = fixedImgWrapper.getBoundingClientRect().bottom;
308      const mediaNodes = fixedImgWrapper.querySelectorAll(
309        "object.svg-animation, img.fixed-media",
310      );
311
312      mediaNodes.forEach((el) => {
313        const style = window.getComputedStyle(el);
314        if (style.display === "none" || style.visibility === "hidden") {
315          return;
316        }
317        const rect = el.getBoundingClientRect();
318        if (rect.height > 0) {
319          mediaBottom = Math.max(mediaBottom, rect.bottom);
320        }
321      });
322
323      return mediaBottom;
324    }
325
326    function getHeaderHeight() {
327      const header = document.querySelector(
328        ".elementor-location-header .header-shortcode-container",
329      );
330      return header ? header.getBoundingClientRect().height : 0;
331    }
332
333    function getVisibleFixedMediaHeight(fixedImgWrapper) {
334      let mediaHeight = fixedImgWrapper.getBoundingClientRect().height;
335      const mediaNodes = fixedImgWrapper.querySelectorAll(
336        "object.svg-animation, img.fixed-media",
337      );
338
339      mediaNodes.forEach((el) => {
340        const style = window.getComputedStyle(el);
341        if (style.display === "none" || style.visibility === "hidden") {
342          return;
343        }
344        const rect = el.getBoundingClientRect();
345        if (rect.height > 0) {
346          mediaHeight = Math.max(mediaHeight, rect.height);
347        }
348      });
349
350      return mediaHeight > 0 ? mediaHeight : 487;
351    }
352
353    function updateFixedImgStickyTop() {
354      if (window.innerWidth < 1024) {
355        $widget[0].style.removeProperty("--indy-acc4-fixed-img-sticky-top");
356        return;
357      }
358
359      const fixedImgEl = $fixedImgWrapper[0];
360      if (!fixedImgEl) return;
361
362      const headerHeight = getHeaderHeight();
363      const mediaHeight = getVisibleFixedMediaHeight(fixedImgEl);
364      const idealTop = (window.innerHeight + headerHeight - mediaHeight) / 2;
365      const stickyTop = Math.max(
366        headerHeight,
367        Math.min(idealTop, window.innerHeight - mediaHeight),
368      );
369
370      $widget[0].style.setProperty(
371        "--indy-acc4-fixed-img-sticky-top",
372        `${stickyTop}px`,
373      );
374    }
375
376    // Fonction pour déterminer l'item le plus proche du centre du viewport
377    function updateSvgOnScroll() {
378      if (window.innerWidth < 1024) {
379        $widget.removeClass("is-media-hidden");
380        return;
381      }
382
383      // Masquer quand le bas du main-wrapper atteint le média sticky ;
384      // réafficher dès qu'on rescrolle au-dessus de ce seuil.
385      // MEDIA_HIDE_OFFSET compense le padding-bottom du .sticky-container
386      // (espace entre le bas de l'image sticky et le CTA).
387      const MEDIA_HIDE_HYSTERESIS = 8;
388      const MEDIA_HIDE_OFFSET = 125;
389      const mainWrapperEl = $mainWrapper[0];
390      const fixedImgEl = $fixedImgWrapper[0];
391      let shouldHideMedia = false;
392
393      if (mainWrapperEl && fixedImgEl) {
394        const mainRect = mainWrapperEl.getBoundingClientRect();
395        const mediaBottom = getVisibleFixedMediaBottom(fixedImgEl);
396        const isHidden = $widget.hasClass("is-media-hidden");
397        const hideThreshold = mediaBottom - MEDIA_HIDE_OFFSET;
398
399        if (isHidden) {
400          shouldHideMedia =
401            mainRect.bottom <= hideThreshold + MEDIA_HIDE_HYSTERESIS;
402        } else {
403          shouldHideMedia =
404            mainRect.bottom <= hideThreshold - MEDIA_HIDE_HYSTERESIS;
405        }
406      }
407
408      $widget.toggleClass("is-media-hidden", shouldHideMedia);
409
410      const viewportCenter = window.innerHeight * 0.5;
411      let $activeItem = null;
412      let minDistance = Infinity;
413
414      $items.each(function () {
415        const $item = $(this);
416        const rect = this.getBoundingClientRect();
417        if (rect.bottom <= 0 || rect.top >= window.innerHeight) return;
418
419        const itemCenter = (rect.top + rect.bottom) * 0.5;
420        const distance = Math.abs(itemCenter - viewportCenter);
421        if (distance < minDistance) {
422          minDistance = distance;
423          $activeItem = $item;
424        }
425      });
426
427      // Si un item est trouvé, charger/changer le média (SVG animé ou image)
428      if ($activeItem && $activeItem.length) {
429        const media = getMediaForItem($activeItem);
430        if (!media || !media.url) return;
431
432        transitionToMedia(media);
433      }
434    }
435
436    // Scroll / resize : une mise à jour par frame (pas de debounce 50ms)
437    let scrollRaf = null;
438    const onScroll = function () {
439      if (scrollRaf) return;
440      scrollRaf = window.requestAnimationFrame(function () {
441        scrollRaf = null;
442        updateSvgOnScroll();
443      });
444    };
445
446    const onResize = function () {
447      if (scrollRaf) return;
448      scrollRaf = window.requestAnimationFrame(function () {
449        scrollRaf = null;
450        updateFixedImgStickyTop();
451        updateSvgOnScroll();
452      });
453    };
454
455    $(window)
456      .off(scrollNs)
457      .on(scrollNs, onScroll)
458      .off(resizeNs)
459      .on(resizeNs, onResize);
460
461    preloadDesktopMedia();
462    updateFixedImgStickyTop();
463    updateSvgOnScroll();
464  }
465
466  // Fonction pour initialiser sur mobile/tablette (< 1024px)
467  // Simple lazy-loading : les SVG se chargent quand ils entrent dans le viewport
468  function initAccompagnement4WidgetMobile() {
469    if (window.innerWidth >= 1024) {
470      return;
471    }
472
473    const $widget = $(".indy-accompagnement-4-widget");
474    if (!$widget.length) return;
475
476    const $items = $widget.find(".content-wrapper .item[data-anim]");
477    if (!$items.length) return;
478
479    // Éviter de recréer plusieurs observers si Elementor ré-initialise le widget
480    if ($widget.data("indyAcc4MobileInited")) return;
481    $widget.data("indyAcc4MobileInited", true);
482
483    // Fonction pour charger le SVG d'un item (lazy-loading)
484    function loadSvgForItem($item) {
485      const $svgObject = $item.find(".item-img object.svg-animation");
486      if (!$svgObject.length) return;
487
488      const svgObjectElement = $svgObject[0];
489      if (!svgObjectElement) return;
490
491      // Vérifier si le SVG est déjà chargé
492      const currentData = svgObjectElement.getAttribute("data");
493      if (currentData && currentData !== "") {
494        return; // Déjà chargé
495      }
496
497      // Récupérer l'URL depuis l'attribut data-anim de l'item
498      const svgUrl = $item.attr("data-anim");
499      if (!svgUrl) return;
500
501      // Charger le SVG
502      svgObjectElement.setAttribute("data", svgUrl);
503      const $fallbackImg = $svgObject.find("img");
504      if ($fallbackImg.length) {
505        $fallbackImg.attr("src", svgUrl);
506      }
507    }
508
509    // Configuration de l'Intersection Observer pour le lazy-loading
510    const observerOptions = {
511      root: null,
512      rootMargin: "-100px 0px", // Attend que l'item soit à 100px dans le viewport avant de charger (ajustable)
513      threshold: 0.2, // Déclenche quand 20% de l'item est visible (ajustable)
514    };
515
516    // Observer pour charger les SVG au fur et à mesure
517    const loadedItems = new Set(); // Pour éviter de charger plusieurs fois le même item
518
519    const observer = new IntersectionObserver((entries) => {
520      entries.forEach((entry) => {
521        const $item = $(entry.target);
522        const itemKey = entry.target;
523
524        // Si l'item entre dans le viewport et n'a pas encore été chargé
525        if (entry.isIntersecting && !loadedItems.has(itemKey)) {
526          loadedItems.add(itemKey);
527          loadSvgForItem($item);
528        }
529      });
530    }, observerOptions);
531
532    // Observer tous les items
533    $items.each(function () {
534      observer.observe(this);
535    });
536
537    // Charger les items déjà visibles au chargement
538    $items.each(function () {
539      const $item = $(this);
540      const rect = this.getBoundingClientRect();
541      const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
542
543      if (isVisible) {
544        const itemKey = this;
545        if (!loadedItems.has(itemKey)) {
546          loadedItems.add(itemKey);
547          loadSvgForItem($item);
548        }
549      }
550    });
551  }
552
553  // Fonction principale qui initialise selon la taille d'écran
554  function initAccompagnement4Widget() {
555    // Permet de réinitialiser proprement lors des resize / re-renders
556    $(".indy-accompagnement-4-widget").removeData("indyAcc4MobileInited");
557    initAccompagnement4WidgetDesktop();
558    initAccompagnement4WidgetMobile();
559  }
560
561  // Initialiser quand le DOM est prêt avec jQuery
562  $(document).ready(function () {
563    initAccompagnement4Widget();
564  });
565
566  function registerElementorHook() {
567    if (typeof elementorFrontend === "undefined" || !elementorFrontend.hooks) {
568      return;
569    }
570
571    elementorFrontend.hooks.addAction(
572      "frontend/element_ready/indy-accompagnement-4-widget.default",
573      function () {
574        initAccompagnement4Widget();
575      },
576    );
577  }
578
579  // Réinitialiser si Elementor recharge le widget
580  $(window).on("elementor/frontend/init", registerElementorHook);
581  registerElementorHook();
582
583  // Réinitialiser lors du redimensionnement de la fenêtre
584  let resizeTimeout;
585  $(window).on("resize", function () {
586    clearTimeout(resizeTimeout);
587    resizeTimeout = setTimeout(function () {
588      initAccompagnement4Widget();
589    }, 250);
590  });
591})(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.