1// Cache des éléments pour éviter les requêtes répétées 2let emblaApiInstance = null; 3let hasTextBeenTruncated = false; // Flag pour éviter les troncatures multiples 4 5// Configuration responsive 6const CAROUSEL_CONFIG = { 7 desktop: { 8 slideWidth: 359, 9 padding: 60, 10 gap: 20 11 }, 12 mobile: { 13 slideWidth: 325, 14 padding: 40, 15 gap: 15 16 } 17}; 18 19// Fonction optimisée pour tronquer le texte - UNE SEULE FOIS 20function truncateText() { 21 if (hasTextBeenTruncated) return; // Ãvite les exécutions multiples 22 23 const newsTeasers = document.querySelectorAll('.carrousel-actu-list .teaser-text'); 24 25 newsTeasers.forEach(teaser => { 26 // Vérifier si déjà tronqué 27 if (teaser.hasAttribute('data-truncated')) return; 28 29 const text = teaser.textContent.trim(); 30 31 if (text.length > 160) { 32 teaser.textContent = text.substring(0, 160) + '...'; 33 teaser.setAttribute('data-truncated', 'true'); 34 } 35 }); 36 37 hasTextBeenTruncated = true; 38} 39 40// Fonction optimisée pour injecter les liens 41function injectLinks(container) { 42 const cards = container.querySelectorAll(".teaser-tile"); 43 44 cards.forEach((card) => { 45 const bodyElement = card.querySelector(".body"); 46 if (!bodyElement || bodyElement.querySelector(".injected-link")) return; 47 48 const originalLink = card.querySelector("a.teaser-link"); 49 const linkUrl = originalLink?.getAttribute("href") || "#"; 50 const linkText = window.actualiteParams.labels.readArticle; 51 const linkContainer = document.createElement("div"); 52 linkContainer.className = "injected-link"; 53 linkContainer.innerHTML = `<a href="${linkUrl}" class="btn piece-btn teaser-btn" title="${linkText}">${linkText}</a>`; 54 55 bodyElement.appendChild(linkContainer); 56 }); 57} 58 59// Fonction optimisée pour créer le wrapper Embla 60function createEmblaWrapper(emblaNode) { 61 const wrapper = document.createElement('div'); 62 wrapper.className = 'embla-wrapper'; 63 64 emblaNode.parentNode.insertBefore(wrapper, emblaNode); 65 wrapper.appendChild(emblaNode); 66 67 return wrapper; 68} 69 70// Fonction optimisée pour styler les slides 71function styleSlides(slides, config) { 72 const slideStyle = ` 73 flex: 0 0 ${config.slideWidth}px !important; 74 min-width: ${config.slideWidth}px !important; 75 max-width: ${config.slideWidth}px !important; 76 width: ${config.slideWidth}px !important; 77 margin: 0 !important; 78 padding: 0 !important; 79 `; 80
81 slides.forEach(slide => { 82 slide.style.cssText = slideStyle; 83 }); 84} 85 86// Fonction optimisée pour créer les boutons 87function createNavigationButtons() { 88 const nav = document.createElement("div"); 89 nav.className = "embla__navigation"; 90 nav.innerHTML = ` 91 <button class="embla__button embla__button--prev" type="button" aria-label="Previous"> 92 <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> 93 <path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 94 </svg> 95 </button> 96 <button class="embla__button embla__button--next" type="button" aria-label="Next"> 97 <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> 98 <path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 99 </svg> 100 </button> 101 `; 102 return nav; 103} 104 105// Fonction optimisée pour gérer les événements 106function setupEventListeners(emblaApi, prevButton, nextButton) { 107 const updateButtons = () => { 108 prevButton.disabled = !emblaApi.canScrollPrev(); 109 let buttonDisabled = isLastElementFullyVisible(); 110 nextButton.disabled = buttonDisabled; 111 }; 112 113 prevButton.addEventListener("click", (e) => { 114 e.preventDefault(); 115 emblaApi.scrollPrev(); 116 //updateButtons(); 117 }, { passive: false }); 118 119 nextButton.addEventListener("click", (e) => { 120 e.preventDefault(); 121 emblaApi.scrollNext(); 122 //updateButtons(); 123 }, { passive: false }); 124 125 emblaApi.on("select", updateButtons); 126 emblaApi.on("settle", updateButtons); 127 emblaApi.on("init", updateButtons); 128// emblaApi.on("settle", updateButtons); 129// emblaApi.on("select", updateButtons); 130 131 132 return updateButtons; 133} 134 135function isLastElementFullyVisible() { 136 let actuList=document.querySelector(".carrousel-actu-list.type-dynamic-list").querySelector(".list-box").querySelector(".embla-wrapper").querySelector(".list-entries"); 137 let derniereActu = actuList.children[actuList.children.length - 1]; 138 const rect = derniereActu.getBoundingClientRect(); 139 140 return ( 141 //rect.top >= 0 && 142 rect.left >= 0 && 143 //rect.bottom <= window.innerHeight && 144 rect.right <= window.innerWidth 145 ); 146} 147 148// Fonction principale optimisée 149function initNewsCarousel() { 150 const container = document.querySelector(".carrousel-actu-list.type-dynamic-list"); 151 if (!container) return; 152 153 // Injection des liens 154 injectLinks(container); 155 156 // Tronquer le texte UNE SEULE FOIS après l'injection des liens 157 truncateText(); 158 159 const emblaNode = container.querySelector(".list-entries.row"); 160 const slides = emblaNode?.querySelectorAll(".list-entry"); 161 162 if (!emblaNode || !slides.length) return; 163 164 const isMobile = window.innerWidth <= 768; 165 const config = isMobile ? CAROUSEL_CONFIG.mobile : CAROUSEL_CONFIG.desktop; 166 167 const emblaWrapper = createEmblaWrapper(emblaNode); 168 169 emblaNode.style.cssText = ` 170 display: flex !important; 171 flex-wrap: nowrap !important; 172 margin: 0 !important; 173 padding: 0 ${config.padding}px ${config.padding}px ${config.padding}px !important; 174 gap: ${config.gap}px !important; 175 `; 176 177 styleSlides(slides, config); 178 179 const options = { 180 align: "start", 181 containScroll: false, 182 dragFree: false, 183 loop: false, 184 slidesToScroll: 1, 185 duration : 20 186 }; 187 188 requestAnimationFrame(() => { 189 try { 190 if (emblaApiInstance) { 191 emblaApiInstance.destroy(); 192 } 193 194 emblaApiInstance = EmblaCarousel(emblaWrapper, options); 195 196 const navigationContainer = createNavigationButtons(); 197 const listBox = container.querySelector(".list-box.list-dynamic"); 198 listBox.parentNode.insertBefore(navigationContainer, listBox.nextSibling); 199 200 const prevButton = navigationContainer.querySelector(".embla__button--prev"); 201 const nextButton = navigationContainer.querySelector(".embla__button--next"); 202 const updateButtons = setupEventListeners(emblaApiInstance, prevButton, nextButton); 203 204 // Double rAF: first frame applies styles, second confirms reflow 205 requestAnimationFrame(() => { 206 requestAnimationFrame(() => { 207 emblaApiInstance.reInit(); 208 updateButtons(); 209 }); 210 }); 211 212 } catch (error) { 213 console.error("Erreur lors de l'initialisation du carousel:", error); 214 } 215 }); 216} 217 218// Optimisation avec debounce pour le resize 219let resizeTimeout; 220function handleResize() { 221 clearTimeout(resizeTimeout); 222 resizeTimeout = setTimeout(() => { 223 if (emblaApiInstance) { 224 emblaApiInstance.reInit(); 225 } 226 }, 10); 227} 228 229// Initialisation optimisée 230document.addEventListener("DOMContentLoaded", () => { 231 setTimeout(initNewsCarousel, 100); 232 window.addEventListener("resize", handleResize, { passive: true }); 233}); 234 235// Nettoyage au déchargement de la page 236window.addEventListener("beforeunload", () => { 237 if (emblaApiInstance) { 238 emblaApiInstance.destroy(); 239 } 240});
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.