From 167950c627dac13e86d6680f2f3da6ba670757ef Mon Sep 17 00:00:00 2001 From: Valentin Le Moign Date: Thu, 10 Sep 2026 01:16:20 +0200 Subject: [PATCH] =?UTF-8?q?Loader=20de=20scroll=20infini=20:=20styles=20r?= =?UTF-8?q?=C3=A9activ=C3=A9s,=20sans=20d=C3=A9calage=20de=20mise=20en=20p?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les règles `.scroll-spinner` et leurs keyframes étaient enfermées dans le bloc `/* … */` legacy de `_category.scss` : Sass recopie ces commentaires verbatim, les trois points n'avaient donc aucun style et restaient invisibles pendant le chargement AJAX. Bloc sorti du commentaire (le legacy reste en place), points allégés (5 px, gris) et marge haute élargie. Le modifieur `--reserve` réserve la place en permanence et n'anime que l'opacité, au lieu de basculer `display` : plus de saut de mise en page ni à l'apparition ni à la disparition. `infiniteScroll.js` bascule désormais la classe `is-loading`. Le `rootMargin` de l'observer passe à 700 px pour que la requête parte plus tôt, et le loader de l'agenda est supprimé (markup + JS). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WQLwtNFa1LNaN7QTAbYkZx --- css/style.css | 34 +++++++++++++++++++++ js/agendaView.js | 4 --- js/infiniteScroll.js | 10 ++++--- scss/_category.scss | 58 ++++++++++++++++++++++++++++++++++++ templates/category.twig | 7 +---- templates/page-annonces.twig | 2 +- templates/search.twig | 2 +- templates/taxonomy.twig | 2 +- 8 files changed, 102 insertions(+), 17 deletions(-) diff --git a/css/style.css b/css/style.css index cfa11d5..d0c1ca8 100755 --- a/css/style.css +++ b/css/style.css @@ -1530,6 +1530,40 @@ footer { } } */ +.scroll-spinner { + display: flex; + justify-content: center; + align-items: center; + gap: 0.35rem; + padding: 3.5rem 0 1.5rem; } + .scroll-spinner__dot { + width: 5px; + height: 5px; + border-radius: 50%; + background-color: #bbbbbb; + animation: scroll-spinner-bounce 1.2s infinite ease-in-out; } + .scroll-spinner__dot:nth-child(2) { + animation-delay: 0.2s; } + .scroll-spinner__dot:nth-child(3) { + animation-delay: 0.4s; } + .scroll-spinner--reserve { + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; } + .scroll-spinner--reserve .scroll-spinner__dot { + animation-play-state: paused; } + .scroll-spinner--reserve.is-loading { + opacity: 1; } + .scroll-spinner--reserve.is-loading .scroll-spinner__dot { + animation-play-state: running; } + +@keyframes scroll-spinner-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.4; } + 40% { + transform: scale(1); + opacity: 1; } } .agenda-view-container { display: none; margin-top: 3rem; } diff --git a/js/agendaView.js b/js/agendaView.js index dba2fe0..c4e1ea1 100644 --- a/js/agendaView.js +++ b/js/agendaView.js @@ -12,7 +12,6 @@ var agendaDone = false; var agendaLoading = false; var swiperWrapper = document.getElementById('agenda-swiper-wrapper'); - var agendaSpinner = document.getElementById('agenda-spinner'); var categoryId = agendaEl.dataset.category || ''; var includeChildren = agendaEl.dataset.includeChildren || '0'; @@ -82,7 +81,6 @@ if (agendaLoading || agendaDone) return; agendaLoading = true; agendaPage++; - agendaSpinner.style.display = 'flex'; var data = new FormData(); data.append('action', 'load_more_agenda'); @@ -98,7 +96,6 @@ fetch(agendaViewData.ajaxUrl, { method: 'POST', body: data }) .then(function (r) { return r.json(); }) .then(function (result) { - agendaSpinner.style.display = 'none'; agendaLoading = false; if (result.success && result.data.html) { var tmp = document.createElement('div'); @@ -118,7 +115,6 @@ } }) .catch(function () { - agendaSpinner.style.display = 'none'; agendaLoading = false; }); } diff --git a/js/infiniteScroll.js b/js/infiniteScroll.js index 8d4e739..513a24e 100644 --- a/js/infiniteScroll.js +++ b/js/infiniteScroll.js @@ -25,7 +25,7 @@ document.addEventListener('DOMContentLoaded', function () { if (loading || done) return; loading = true; page++; - spinner.style.display = 'flex'; + if (spinner) spinner.classList.add('is-loading'); var data = new FormData(); data.append('action', 'load_more_posts'); @@ -46,7 +46,7 @@ document.addEventListener('DOMContentLoaded', function () { fetch(infiniteScrollData.ajaxUrl, { method: 'POST', body: data }) .then(function (response) { return response.json(); }) .then(function (result) { - spinner.style.display = 'none'; + if (spinner) spinner.classList.remove('is-loading'); if (result.success && result.data.html) { grid.insertAdjacentHTML('beforeend', result.data.html); loading = false; @@ -56,16 +56,18 @@ document.addEventListener('DOMContentLoaded', function () { } }) .catch(function () { - spinner.style.display = 'none'; + if (spinner) spinner.classList.remove('is-loading'); loading = false; }); } + // Marge large : la requête part bien avant que la sentinelle soit à + // l'écran, les cards sont souvent déjà en place quand on arrive en bas var observer = new IntersectionObserver(function (entries) { if (entries[0].isIntersecting) { fetchPosts(); } - }, { rootMargin: '200px' }); + }, { rootMargin: '700px' }); observer.observe(sentinel); }); diff --git a/scss/_category.scss b/scss/_category.scss index 361262a..7dc15c0 100644 --- a/scss/_category.scss +++ b/scss/_category.scss @@ -405,6 +405,64 @@ } */ +// ── Infinite scroll spinner ─────────────────────────────────────────────────── + +.scroll-spinner { + display: flex; + justify-content: center; + align-items: center; + gap: 0.35rem; + padding: 3.5rem 0 1.5rem; + + &__dot { + width: 5px; + height: 5px; + border-radius: 50%; + background-color: $less-less-light-gray; + animation: scroll-spinner-bounce 1.2s infinite ease-in-out; + + &:nth-child(2) { + animation-delay: 0.2s; + } + + &:nth-child(3) { + animation-delay: 0.4s; + } + } + + // Scroll infini : la place est réservée en permanence (on ne bascule pas + // display), sinon l'apparition des points décale la mise en page juste + // avant l'insertion des cards + &--reserve { + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; + + .scroll-spinner__dot { + animation-play-state: paused; + } + + &.is-loading { + opacity: 1; + + .scroll-spinner__dot { + animation-play-state: running; + } + } + } +} + +@keyframes scroll-spinner-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + // ── Agenda slider view ──────────────────────────────────────────────────────── .agenda-view-container { diff --git a/templates/category.twig b/templates/category.twig index e5b0fe0..b207f7e 100644 --- a/templates/category.twig +++ b/templates/category.twig @@ -98,7 +98,7 @@ data-axe="{{ active_axe }}" data-date-from="{{ active_date_from }}" data-date-to="{{ active_date_to }}"> - - diff --git a/templates/page-annonces.twig b/templates/page-annonces.twig index cf3096a..c49b4fe 100644 --- a/templates/page-annonces.twig +++ b/templates/page-annonces.twig @@ -31,7 +31,7 @@ data-axe="{{ active_axe }}" data-date-from="{{ active_date_from }}" data-date-to="{{ active_date_to }}"> -