hackertours/assets/js/gallery.js

116 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Gallery shortcode: opens the full image in a lightbox (a modal <dialog>) with its alt text
// and credit. Arrow buttons, arrow keys and swiping move between the images of the gallery;
// Escape, the close button or a click beside the image close it.
{
const button = (className, label, symbol) => {
const element = document.createElement('button');
element.type = 'button';
element.className = `lightbox__button ${className}`;
element.setAttribute('aria-label', label);
element.innerHTML = `<span aria-hidden="true">${symbol}</span>`;
return element;
};
const lightbox = (gallery) => {
const links = [...gallery.querySelectorAll('.gallery__link')];
const labels = gallery.dataset;
let index = 0;
const dialog = document.createElement('dialog');
dialog.className = 'lightbox';
dialog.setAttribute('aria-label', labels.labelDialog);
dialog.innerHTML = `
<figure class="lightbox__figure">
<img class="lightbox__image" alt="">
<figcaption class="lightbox__caption" aria-live="polite">
<span class="lightbox__alt"></span>
<span class="lightbox__credit"></span>
<span class="lightbox__counter"></span>
</figcaption>
</figure>`;
const close = button('lightbox__close', labels.labelClose, '×');
const previous = button('lightbox__previous', labels.labelPrevious, '‹');
const next = button('lightbox__next', labels.labelNext, '›');
dialog.append(close);
if (links.length > 1) dialog.append(previous, next);
document.body.append(dialog);
const image = dialog.querySelector('.lightbox__image');
const preload = (link) => {
new Image().src = link.href;
};
const show = (newIndex) => {
index = (newIndex + links.length) % links.length;
const link = links[index];
image.src = link.href;
image.width = link.dataset.width;
image.height = link.dataset.height;
image.alt = link.dataset.alt;
// The alt text is shown as the caption, so screen readers get it once, from the image.
dialog.querySelector('.lightbox__alt').textContent = link.dataset.alt;
dialog.querySelector('.lightbox__alt').setAttribute('aria-hidden', 'true');
dialog.querySelector('.lightbox__credit').textContent = link.dataset.credit ?? '';
dialog.querySelector('.lightbox__counter').textContent =
links.length > 1 ? `${index + 1} / ${links.length}` : '';
if (links.length > 1) {
preload(links[(index + 1) % links.length]);
preload(links[(index - 1 + links.length) % links.length]);
}
};
links.forEach((link, i) => {
link.addEventListener('click', (event) => {
// Let modified clicks open the image in a new tab or window as usual.
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
show(i);
dialog.showModal();
close.focus();
});
});
// Closing goes through here, so that focus goes back to the thumbnail of the image that was
// shown (the browser would restore it to where it was before the lightbox opened).
const hide = () => {
dialog.close();
links[index].focus();
};
close.addEventListener('click', hide);
// Escape, or the back gesture on Android.
dialog.addEventListener('cancel', (event) => {
event.preventDefault();
hide();
});
previous.addEventListener('click', () => show(index - 1));
next.addEventListener('click', () => show(index + 1));
// A click on the dark area around the image closes the lightbox.
dialog.addEventListener('click', (event) => {
if (event.target === dialog || event.target.classList.contains('lightbox__figure')) hide();
});
dialog.addEventListener('keydown', (event) => {
if (links.length < 2) return;
if (event.key === 'ArrowLeft') show(index - 1);
else if (event.key === 'ArrowRight') show(index + 1);
else return;
event.preventDefault();
});
// Swiping left or right on touch screens.
let startX = null;
dialog.addEventListener('pointerdown', (event) => {
if (event.pointerType !== 'mouse') startX = event.clientX;
});
dialog.addEventListener('pointerup', (event) => {
if (startX === null || links.length < 2) return;
const distance = event.clientX - startX;
startX = null;
if (Math.abs(distance) > 50) show(index + (distance < 0 ? 1 : -1));
});
};
document.querySelectorAll('[data-gallery]').forEach(lightbox);
}