217 lines
No EOL
6.9 KiB
JavaScript
217 lines
No EOL
6.9 KiB
JavaScript
|
|
import { initSwipers } from './swipers.js';
|
|
|
|
export function report(responsiveSmall) {
|
|
if (document.body.dataset.template === 'report') {
|
|
|
|
// Initialiser tous les sliders de type before-after
|
|
initSliderBeforeAfter();
|
|
|
|
// Ne fonctionne que pour les écrans plus grands que responsiveSmall
|
|
if (window.matchMedia(responsiveSmall).matches) {
|
|
// Sur mobile : initialiser les swipers normalement car initMediaDisplay ne sera pas actif
|
|
initSwipers();
|
|
return;
|
|
}
|
|
|
|
// Sur desktop : initMediaDisplay va gérer les media dynamiquement
|
|
// Les swipers seront initialisés au moment de l'insertion dans #report__medias
|
|
initMediaDisplay();
|
|
}
|
|
}
|
|
|
|
|
|
function initSliderBeforeAfter(container = document){
|
|
const slidersBeforeAfter = container.querySelectorAll('.slider-before-after');
|
|
slidersBeforeAfter.forEach(function (sliderContainer, index) {
|
|
const sliderInput = sliderContainer.querySelector('.slider');
|
|
if (sliderInput) {
|
|
sliderInput.addEventListener('input', (e) => {
|
|
console.log('slider value:', e.target.value);
|
|
sliderContainer.style.setProperty('--position', `${e.target.value}%`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function initMediaDisplay() {
|
|
const reportMedias = document.querySelector('#report__medias');
|
|
if (!reportMedias) return;
|
|
|
|
// Calculer la hauteur depuis les variables CSS: calc(var(--header-h) + var(--padding-body))
|
|
const rootStyles = getComputedStyle(document.documentElement);
|
|
const headerH = rootStyles.getPropertyValue('--header-h').trim();
|
|
const paddingBody = rootStyles.getPropertyValue('--padding-body').trim();
|
|
|
|
// Convertir en pixels si nécessaire
|
|
const headerHPx = parseFloat(headerH);
|
|
const paddingBodyPx = parseFloat(paddingBody);
|
|
const totalOffset = headerHPx + paddingBodyPx;
|
|
|
|
const sections = document.querySelectorAll('.section-content');
|
|
const mediaElements = [];
|
|
let mediaCounter = 0;
|
|
|
|
// 1. Pour chaque section, traiter les .media
|
|
sections.forEach((section) => {
|
|
const medias = section.querySelectorAll('.media');
|
|
|
|
medias.forEach((media) => {
|
|
// Générer un ID unique si nécessaire
|
|
if (!media.id) {
|
|
media.id = `media-${mediaCounter++}`;
|
|
}
|
|
|
|
// Créer une ancre
|
|
const anchor = document.createElement('div');
|
|
anchor.className = 'media-anchor';
|
|
anchor.dataset.mediaId = media.id;
|
|
anchor.innerHTML = '<span class="arrow-report">▶</span>'
|
|
|
|
// Vérifier si le media est précédé d'un titre
|
|
let previousElement = media.previousElementSibling;
|
|
let insertBeforeElement = media;
|
|
|
|
// Si l'élément précédent est un titre (h1-h6), insérer l'ancre avant le titre
|
|
if (previousElement && /^H[1-6]$/.test(previousElement.tagName)) {
|
|
insertBeforeElement = previousElement;
|
|
}
|
|
|
|
// Insérer l'ancre
|
|
insertBeforeElement.parentNode.insertBefore(anchor, insertBeforeElement);
|
|
|
|
// Stocker la référence pour l'observer
|
|
mediaElements.push({
|
|
anchor: anchor,
|
|
media: media.cloneNode(true), // Cloner le media
|
|
originalMedia: media,
|
|
section: section
|
|
});
|
|
|
|
// Masquer le media original
|
|
media.style.display = 'none';
|
|
});
|
|
});
|
|
|
|
// 2. Fonction pour trouver et afficher le media le plus proche de la ligne de déclenchement
|
|
let currentMediaId = null;
|
|
let isUpdating = false; // Flag pour éviter les mises à jour simultanées
|
|
|
|
function updateActiveMedia() {
|
|
// Éviter les mises à jour simultanées
|
|
if (isUpdating) return;
|
|
|
|
// Trouver l'ancre qui est la plus proche de la ligne de déclenchement (totalOffset du haut)
|
|
let closestAnchor = null;
|
|
let closestDistance = Infinity;
|
|
|
|
mediaElements.forEach(({ anchor }) => {
|
|
const rect = anchor.getBoundingClientRect();
|
|
|
|
// Si l'ancre est au-dessus ou à la ligne de déclenchement
|
|
if (rect.top <= totalOffset) {
|
|
const distance = totalOffset - rect.top;
|
|
|
|
// Prendre celle qui vient juste de passer (la plus proche en dessous de la ligne)
|
|
if (distance < closestDistance) {
|
|
closestDistance = distance;
|
|
closestAnchor = anchor;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Si on a trouvé une ancre
|
|
if (closestAnchor) {
|
|
const mediaId = closestAnchor.dataset.mediaId;
|
|
|
|
// Si c'est déjà le media affiché, ne rien faire
|
|
if (currentMediaId === mediaId) return;
|
|
|
|
// Trouver le media correspondant
|
|
const mediaData = mediaElements.find(m => m.anchor === closestAnchor);
|
|
|
|
if (mediaData) {
|
|
isUpdating = true;
|
|
|
|
// Utiliser requestAnimationFrame pour éviter les conflits de reflow
|
|
requestAnimationFrame(() => {
|
|
// Vider le conteneur
|
|
reportMedias.innerHTML = '';
|
|
|
|
// Ajouter le nouveau media
|
|
const newMediaElement = mediaData.media.cloneNode(true);
|
|
reportMedias.appendChild(newMediaElement);
|
|
currentMediaId = mediaId;
|
|
|
|
// Attendre le prochain frame pour initialiser les sliders/swipers
|
|
requestAnimationFrame(() => {
|
|
initSliderBeforeAfter(reportMedias);
|
|
initSwipers(reportMedias);
|
|
|
|
// Débloquer les mises à jour après un court délai
|
|
setTimeout(() => {
|
|
isUpdating = false;
|
|
}, 100);
|
|
});
|
|
});
|
|
}
|
|
} else {
|
|
// Aucune ancre n'a encore franchi la ligne, vider le conteneur
|
|
if (currentMediaId !== null) {
|
|
reportMedias.innerHTML = '';
|
|
currentMediaId = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Écouter le scroll
|
|
let scrollTimeout;
|
|
window.addEventListener('scroll', () => {
|
|
// Throttle pour optimiser les performances
|
|
if (scrollTimeout) return;
|
|
|
|
scrollTimeout = setTimeout(() => {
|
|
updateActiveMedia();
|
|
scrollTimeout = null;
|
|
}, 10);
|
|
});
|
|
|
|
// Appeler une première fois au chargement
|
|
updateActiveMedia();
|
|
|
|
// 4. Gérer les sections sans media immédiat
|
|
// Observer aussi les sections elles-mêmes
|
|
const sectionObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const section = entry.target;
|
|
|
|
// Vérifier si cette section a un media juste après son premier titre
|
|
const firstTitle = section.querySelector('h1, h2, h3, h4, h5, h6');
|
|
if (firstTitle) {
|
|
let nextElement = firstTitle.nextElementSibling;
|
|
|
|
// Chercher le prochain élément qui n'est pas une ancre
|
|
while (nextElement && nextElement.classList.contains('media-anchor')) {
|
|
nextElement = nextElement.nextElementSibling;
|
|
}
|
|
|
|
// Si le prochain élément n'est pas un .media, vider #report__medias
|
|
if (!nextElement || !nextElement.classList.contains('media')) {
|
|
reportMedias.innerHTML = '';
|
|
currentMediaId = null;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}, {
|
|
root: null,
|
|
rootMargin: `-${totalOffset}px 0px 0px 0px`,
|
|
threshold: 0
|
|
});
|
|
|
|
// Observer toutes les sections
|
|
sections.forEach(section => {
|
|
sectionObserver.observe(section);
|
|
});
|
|
} |