163 lines
4.8 KiB
JavaScript
163 lines
4.8 KiB
JavaScript
|
|
import Swiper from 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.mjs';
|
||
|
|
|
||
|
|
const DESKTOP = window.matchMedia('(min-width: 1080px)');
|
||
|
|
|
||
|
|
// Inline du close.svg (assets/icons/close.svg)
|
||
|
|
const CLOSE_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><rect y="22.3249" width="31.1111" height="2.22222" transform="rotate(-45 0 22.3249)"/><rect x="1.80078" width="31.1111" height="2.22222" transform="rotate(45 1.80078 0)"/></svg>`;
|
||
|
|
|
||
|
|
let lightboxEl = null;
|
||
|
|
let lightboxSwiper = null;
|
||
|
|
|
||
|
|
function createLightboxDOM() {
|
||
|
|
const el = document.createElement('div');
|
||
|
|
el.id = 'lightbox';
|
||
|
|
el.setAttribute('aria-modal', 'true');
|
||
|
|
el.setAttribute('role', 'dialog');
|
||
|
|
el.setAttribute('aria-label', 'Image agrandie');
|
||
|
|
el.setAttribute('inert', '');
|
||
|
|
|
||
|
|
el.innerHTML = `
|
||
|
|
<button id="lightbox-close" aria-label="Fermer">${CLOSE_SVG}</button>
|
||
|
|
<div id="lightbox-swiper" class="swiper">
|
||
|
|
<div class="swiper-wrapper"></div>
|
||
|
|
<div class="swiper-button-prev"></div>
|
||
|
|
<div class="swiper-button-next"></div>
|
||
|
|
<div class="swiper-pagination"></div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
|
||
|
|
document.body.appendChild(el);
|
||
|
|
|
||
|
|
el.querySelector('#lightbox-close').addEventListener('click', closeLightbox);
|
||
|
|
|
||
|
|
// Fermeture au clic sur le fond (pas sur le swiper)
|
||
|
|
el.addEventListener('click', (e) => {
|
||
|
|
if (e.target === el) closeLightbox();
|
||
|
|
});
|
||
|
|
|
||
|
|
return el;
|
||
|
|
}
|
||
|
|
|
||
|
|
function ensureLightboxDOM() {
|
||
|
|
if (!lightboxEl) {
|
||
|
|
lightboxEl = createLightboxDOM();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function getImageData(figure) {
|
||
|
|
const img = figure.querySelector('img');
|
||
|
|
|
||
|
|
// 1. figcaption dans la figure
|
||
|
|
// 2. p.caption dans la figure (horizontal-gallery)
|
||
|
|
// 3. p.caption sibling de la figure (covers : resource, news-item, impact…)
|
||
|
|
const captionEl =
|
||
|
|
figure.querySelector('figcaption') ||
|
||
|
|
figure.querySelector('p.caption') ||
|
||
|
|
(figure.nextElementSibling?.matches('p.caption') ? figure.nextElementSibling : null);
|
||
|
|
|
||
|
|
return {
|
||
|
|
src: img?.src || '',
|
||
|
|
alt: img?.alt || '',
|
||
|
|
caption: captionEl?.innerHTML || '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildSlides(images) {
|
||
|
|
const wrapper = lightboxEl.querySelector('.swiper-wrapper');
|
||
|
|
wrapper.innerHTML = '';
|
||
|
|
|
||
|
|
images.forEach(({ src, alt, caption }) => {
|
||
|
|
const slide = document.createElement('div');
|
||
|
|
slide.className = 'swiper-slide';
|
||
|
|
slide.innerHTML = `
|
||
|
|
<figure>
|
||
|
|
<img src="${src}" alt="${alt}">
|
||
|
|
${caption ? `<figcaption>${caption}</figcaption>` : ''}
|
||
|
|
</figure>
|
||
|
|
`;
|
||
|
|
wrapper.appendChild(slide);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function openLightbox(images, startIndex = 0) {
|
||
|
|
ensureLightboxDOM();
|
||
|
|
buildSlides(images);
|
||
|
|
|
||
|
|
const swiperEl = lightboxEl.querySelector('#lightbox-swiper');
|
||
|
|
|
||
|
|
// Détruire l'instance précédente si elle existe
|
||
|
|
if (swiperEl.swiper) {
|
||
|
|
swiperEl.swiper.destroy(true, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
lightboxSwiper = new Swiper(swiperEl, {
|
||
|
|
slidesPerView: 1,
|
||
|
|
initialSlide: startIndex,
|
||
|
|
speed: 400,
|
||
|
|
watchOverflow: true,
|
||
|
|
keyboard: { enabled: true },
|
||
|
|
navigation: {
|
||
|
|
nextEl: swiperEl.querySelector('.swiper-button-next'),
|
||
|
|
prevEl: swiperEl.querySelector('.swiper-button-prev'),
|
||
|
|
},
|
||
|
|
pagination: {
|
||
|
|
el: swiperEl.querySelector('.swiper-pagination'),
|
||
|
|
clickable: true,
|
||
|
|
},
|
||
|
|
a11y: {
|
||
|
|
prevSlideMessage: 'Image précédente',
|
||
|
|
nextSlideMessage: 'Image suivante',
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
lightboxEl.removeAttribute('inert');
|
||
|
|
document.body.classList.add('lightbox-open');
|
||
|
|
lightboxEl.querySelector('#lightbox-close').focus();
|
||
|
|
}
|
||
|
|
|
||
|
|
function closeLightbox() {
|
||
|
|
document.body.classList.remove('lightbox-open');
|
||
|
|
lightboxEl.setAttribute('inert', '');
|
||
|
|
}
|
||
|
|
|
||
|
|
function isEligible(figure) {
|
||
|
|
// Exclure les figures dans un <a>
|
||
|
|
if (figure.closest('a')) return false;
|
||
|
|
// Exclure si le parent direct contient un .link-block (card navigable)
|
||
|
|
if (figure.parentElement.querySelector(':scope > .link-block')) return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function initLightbox() {
|
||
|
|
// Fermeture à la touche Echap
|
||
|
|
document.addEventListener('keydown', (e) => {
|
||
|
|
if (e.key === 'Escape' && document.body.classList.contains('lightbox-open')) {
|
||
|
|
closeLightbox();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
document.querySelectorAll('figure').forEach((figure) => {
|
||
|
|
if (!isEligible(figure)) return;
|
||
|
|
|
||
|
|
// Marquer les figures éligibles (pour le cursor CSS)
|
||
|
|
figure.setAttribute('data-lightbox', '');
|
||
|
|
|
||
|
|
figure.addEventListener('click', () => {
|
||
|
|
if (!DESKTOP.matches) return;
|
||
|
|
|
||
|
|
const slide = figure.closest('.swiper-slide');
|
||
|
|
|
||
|
|
if (slide) {
|
||
|
|
// Galerie : ouvrir toutes les images du swiper parent
|
||
|
|
const swiperEl = slide.closest('.swiper');
|
||
|
|
const allFigures = [...swiperEl.querySelectorAll('.swiper-slide > figure')];
|
||
|
|
const startIndex = allFigures.indexOf(figure);
|
||
|
|
openLightbox(allFigures.map(getImageData), startIndex);
|
||
|
|
} else {
|
||
|
|
// Image standalone
|
||
|
|
openLightbox([getImageData(figure)], 0);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|