Snappy Lightbox for gallery

I broke the original squarespace lightbox when creating functionality for the visitors to be able to manage collections of images from my art section, more on that in a later blog post, perhaps. In the meantime I wanted to have a decent image previewing mode. So I wrote a lightbox. A lightbox is an overlay element which allows to more closely inspect individual images from the gallery. Currently if you click outside of the image, or the buttons, it closes the lightbox, unless you are in zoom mode. I implemented lazy loading and a preloader, in case the images take some time to load. If you click the image you get to pan around a zoomed version of it. The arrow keys now allow viewing the previous and next image in the gallery. Very happy with the result. In the code you will find small sections pertaining to a popover, which is my replacement for the stock added-to-cart pop-up. It is described in an earlier post and I am using it frequently instead of alerts. There is also a bit of handy code to determine with certainty whether you are in the mobile version of the site, this I also re-use abundantly. I coded the lightbox for the masonry gallery, which should have the lightbox option enabled, as I use the overlays provided by it, subjugating their link to fit my purposes. If you want to customize the placeholder loader, look in the CSS. I also have two buttons in the top right. These allow viewing the unique identifier of the image and buying the image. To view the process of buying the image, please see my post about auto filling forms. To see the lightbox in action, go to my art section and choose any gallery. Upon clicking on an image it will snappily pop up and light up your day.

HTML:

JAVASCRIPT:

<script>
document.addEventListener('DOMContentLoaded', function () {
const gallerySection = document.querySelector('.gallery-masonry');
if (gallerySection) {
//exlude some gallery containing pages from buy and info ability
let noInfoBuy = false;
const getURL = window.location.href;
const badStrings = ['/avanti', 'posters-and-layouts', 'cards-and-logos', '/jumper'];
if (badStrings.some(badString => getURL.includes(badString))) {
noInfoBuy = true;
}
let lightbox;
let currentIndex = 0;
let galleryItems = [];
let isZoomed = false;
let isInfo = false;
//mobile mode check
const headerActions = document.querySelector('.header-actions');
let isTouch = false;
function checkHeader() {
const styles = window.getComputedStyle(headerActions);
isTouch = styles.getPropertyValue('display') !== 'flex';
}
checkHeader();
if (!isTouch || isTouch) { //redundant check
//initialize popover
let hideTimeout;
var popover = document.getElementById('custom-popover');
var popoverMessage = document.getElementById('popover-message');
function showPopover(message) {
popoverMessage.textContent = message;
popover.classList.add('show');
popover.style.pointerEvents = 'auto';
clearTimeout(hideTimeout);
hideTimeout = setTimeout(function() {
popover.classList.remove('show');
popover.style.pointerEvents = 'none';
}, 3000);
}
//copy to clipboard
function copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
$('#popoverMessage').off('click');
popoverMessage.style.color = "#ffc700";
showPopover('Tag copied to clipboard');
}).catch(err => {
$('#popoverMessage').off('click');
popoverMessage.style.color = "#ea4b1a";
showPopover('Tag failed to copy');
});
} else {
console.error('Clipboard API not supported');
}
}
//lightbox
function hideInfo() {
const infoButton = lightbox.querySelector('.custom-info-button');
const copyButton = lightbox.querySelector('.custom-copy-button');
const sideButtonContainer = lightbox.querySelector('.custom-lightbox-button-container');
infoButton.textContent = 'VIEW TAG';
sideButtonContainer.classList.remove('shown'); 
infoButton.classList.remove('shown');
if (copyButton) {
copyButton.remove();
}
isInfo = false;
}
function showInfo() {
const infoButton = lightbox.querySelector('.custom-info-button');
const sideButtonContainer = lightbox.querySelector('.custom-lightbox-button-container');
infoButton.textContent = galleryItems[currentIndex].alt || 'No description';
infoButton.classList.add('shown');
sideButtonContainer.classList.add('shown');  
const copyButton = document.createElement('div');
copyButton.classList.add('custom-copy-button');
copyButton.textContent = 'COPY';
infoButton.appendChild(copyButton);
copyButton.addEventListener('click', function(event) {
const textToCopy = infoButton.textContent;
sessionStorage.setItem('uniqueTag', textToCopy);
copyToClipboard(textToCopy);
});  
isInfo = true;
}
function toggleInfo() {
const sideButtonContainer = lightbox.querySelector('.custom-lightbox-button-container');
if (isZoomed) {
hideInfo(); 
sideButtonContainer.style.display = 'none';
} else {
if (noInfoBuy == false) {
sideButtonContainer.style.display = ''; 
}
}
}
function moveZoomedImage(event) {
const lightboxImg = document.querySelector('.custom-lightbox-content');
const rect = lightboxImg.getBoundingClientRect();
const offsetX = event.clientX - rect.left;
const offsetY = event.clientY - rect.top;
const moveX = ((offsetX / rect.width) * 50) - 25;
const moveY = ((offsetY / rect.height) * 50) - 25;
const maxMoveX = Math.max(Math.min(moveX, 25), -25);
const maxMoveY = Math.max(Math.min(moveY, 25), -25);
lightboxImg.style.transform = `scale(2) translate(${-maxMoveX}%, ${-maxMoveY}%)`;
}
function toggleZoom(event) {
const lightboxImg = document.querySelector('.custom-lightbox-content');
if (isZoomed) {
lightboxImg.classList.remove('zoomed');
lightboxImg.style.transform = '';
lightboxImg.style.cursor = 'zoom-in';
lightbox.removeEventListener('mousemove', moveZoomedImage);
} else {
lightboxImg.classList.add('zoomed');
lightboxImg.style.cursor = 'zoom-out';
lightbox.addEventListener('mousemove', moveZoomedImage);
}
isZoomed = !isZoomed;
toggleInfo();
}
function setupLightbox() {
if (!lightbox) {
lightbox = document.createElement('div');
lightbox.className = 'custom-lightbox';
lightbox.id = 'custom-lightbox';
const placeholder = document.createElement('div');
placeholder.className = 'custom-lightbox-placeholder';
const placeholderContainer = document.createElement('div');
placeholderContainer.className = 'custom-lightbox-placeholder-container lds-heart';
placeholderContainer.appendChild(placeholder);
lightbox.appendChild(placeholderContainer);
const lightboxImg = document.createElement('img');
lightboxImg.className = 'custom-lightbox-content';
lightboxImg.id = 'custom-lightbox-img';
lightboxImg.style.display = 'none';
lightbox.appendChild(lightboxImg);
const navigation = document.createElement('div');
navigation.className = 'custom-navigation';
const prevBtn = document.createElement('div');
prevBtn.className = 'custom-prev';
prevBtn.innerHTML = '❮';
navigation.appendChild(prevBtn);
const nextBtn = document.createElement('div');
nextBtn.className = 'custom-next';
nextBtn.innerHTML = '❯';
navigation.appendChild(nextBtn);
lightbox.appendChild(navigation);
const sideButtonContainer = document.createElement('div');
sideButtonContainer.className = 'custom-lightbox-button-container';  
const infoButton = document.createElement('div');
infoButton.className = 'custom-info-button';
infoButton.textContent = 'VIEW TAG';  
const buyButton = document.createElement('div');
buyButton.className = 'custom-lightbox-buy-button';
buyButton.textContent = 'ACQUIRE';
sideButtonContainer.appendChild(infoButton);
sideButtonContainer.appendChild(buyButton); 
lightbox.appendChild(sideButtonContainer);
document.body.appendChild(lightbox);
if (noInfoBuy == true) {
sideButtonContainer.style.display = 'none';
}
lightbox.addEventListener('click', function (event) {
if (event.target === lightbox || !lightbox.contains(event.target)) {
if (isZoomed) {
lightboxImg.classList.remove('zoomed');
lightboxImg.style.transform = '';
lightboxImg.style.cursor = 'zoom-in';
lightbox.removeEventListener('mousemove', moveZoomedImage);
isZoomed = false;
toggleInfo();
} else {
if (sideButtonContainer.classList.contains('shown')) {
infoButton.textContent = 'VIEW TAG';
infoButton.classList.remove('shown'); 
sideButtonContainer.classList.remove('shown'); 
} else {
closeLightbox();
}
}
} else if (event.target.classList.contains('custom-prev') || event.target.classList.contains('custom-next')) {
if (event.target.classList.contains('custom-prev')) {
currentIndex = (currentIndex > 0) ? currentIndex - 1 : galleryItems.length - 1;
loadImage(currentIndex);
} else if (event.target.classList.contains('custom-next')) {
currentIndex = (currentIndex < galleryItems.length - 1) ? currentIndex + 1 : 0;
loadImage(currentIndex);
}
} else if (event.target === lightboxImg) {
if (!isTouch) {
toggleZoom(event);
}
} else if (event.target.classList.contains('custom-info-button')) {
isInfo ? hideInfo() : showInfo();
} else if (event.target.classList.contains('custom-lightbox-buy-button')) {
const textToCopy = galleryItems[currentIndex].alt;
const imageURL = galleryItems[currentIndex].src;
sessionStorage.setItem('uniqueTag', textToCopy);
sessionStorage.setItem('imageURL', imageURL);
copyToClipboard(textToCopy);  
let productURL = "/shop/p/original-poster";
let queryParam = "view";
let queryValue = "acquire";
let fullURL = `${productURL}?${queryParam}=${queryValue}`;
window.location.href = fullURL;
}
});
}
}
function preloadImages(imageUrls) {
imageUrls.forEach(url => {
const img = new Image();
img.src = url;
});
}
function arrowKeydown(event) {
const prevBtn = lightbox.querySelector('.custom-prev');
const nextBtn = lightbox.querySelector('.custom-next');
if (event.key === 'ArrowLeft') {
prevBtn.click();
}
if (event.key === 'ArrowRight') {
nextBtn.click();
}
}
function loadImage(index) {
const lightboxImg = lightbox.querySelector('#custom-lightbox-img');
const item = galleryItems[index];
lightboxImg.classList.remove('zoomed');
lightboxImg.style.transform = '';
lightboxImg.style.cursor = 'zoom-in';
isZoomed = false;
toggleInfo();
lightbox.removeEventListener('mousemove', moveZoomedImage);
const placeholderContainer = lightbox.querySelector('.custom-lightbox-placeholder-container');     
placeholderContainer.style.display = 'flex';
lightboxImg.style.display = 'none';
lightbox.style.display = 'flex';
document.body.style.overflow = 'hidden';
lightboxImg.src = item.src; 
lightboxImg.alt = item.alt;
lightboxImg.onload = function () {
placeholderContainer.style.display = 'none';
lightboxImg.style.display = 'block';
hideInfo();
lightboxImg.style.zIndex = '9999'; 
};
}
function openLightbox(index) {
if (galleryItems.length === 0) {
galleryItems = refreshGalleryItems();
preloadImages(galleryItems);
}
document.addEventListener('keydown', arrowKeydown);
currentIndex = index;
setupLightbox();
loadImage(currentIndex);
}
function closeLightbox() {
if (lightbox) {
const lightboxImg = lightbox.querySelector('#custom-lightbox-img');
lightboxImg.classList.remove('zoomed');
lightboxImg.style.transform = '';
lightboxImg.style.cursor = 'zoom-in';
isZoomed = false;
hideInfo();
lightbox.removeEventListener('mousemove', moveZoomedImage);
document.removeEventListener('keydown', arrowKeydown);
lightbox.style.display = 'none';
document.body.style.overflow = '';
}
}
function refreshGalleryItems() {
const gallery = gallerySection.querySelector('.gallery-masonry-wrapper');
const galleryLinkElements = gallery.querySelectorAll('.gallery-masonry-lightbox-link');
return Array.from(galleryLinkElements).map(link => {
const img = link.querySelector('img');
const imgAlt = img.getAttribute('alt');
const imgSrc = img.getAttribute('src');
return {
src: imgSrc,
alt: imgAlt
}; 
});
}
galleryItems = refreshGalleryItems();
setupLightbox();
function setupGalleryEventListeners() {
const galleryLinkElements = gallerySection.querySelectorAll('.gallery-masonry-lightbox-link');
galleryLinkElements.forEach((item, index) => {
item.addEventListener('click', function (event) {
event.preventDefault();
openLightbox(index); 
});
});
}
setupGalleryEventListeners();
function updateLinks() {
const gallery = gallerySection.querySelector('.gallery-masonry-wrapper');
const galleryLinkElements = gallery.querySelectorAll('.gallery-masonry-lightbox-link');
galleryLinkElements.forEach((link) => {
link.setAttribute('href', '');
});
}
updateLinks();
const observer = new MutationObserver(function () {
updateLinks();
galleryItems = refreshGalleryItems();
setupGalleryEventListeners();
});
observer.observe(gallerySection, { childList: true, subtree: true });
}
}
});
</script>
CSS:

//purchase and tag
.custom-lightbox-button-container {
  justify-content: center;
  position: absolute;
  color: #ffc700;
  top: 2.5%; 
  right: 0;
  margin-right: -40px;
  transform: translateX(-50%);
  padding: 0;
  background-color: #000000;
  border: 0;
  cursor: pointer;
  z-index: 10000;
  display: block;
  text-align: center;
  letter-spacing: 2px;
  height: auto;
  max-width: 400px;
}
.custom-lightbox-button-container.shown {
  margin-right: -170px;
}
.custom-lightbox-buy-button {
  color: #ffc700;
  padding: 10px 20px;
  margin-top: 20px;
  background-color: #000000;
  border: solid 2px #ffc700;
  cursor: pointer;
  z-index: 10000;
  display: block;
  text-align: center;
  letter-spacing: 2px;
  transition: all 0.3s ease;
  height: auto;
  right: 0;
}
.custom-copy-button {
  color: #ffc700;
  padding: 10px 20px;
  margin-top: 20px;
  margin-bottom: 10px;
  background-color: #000000;
  border: solid 2px #ffc700;
  cursor: pointer;
  z-index: 10000;
  display: block;
  text-align: center;
  letter-spacing: 2px;
  transition: all 0.3s ease;
  height: auto;
  right: 20px;
}
.custom-lightbox-buy-button:hover {
  color: #000000;
  background-color: #ffc700;
  border: solid 2px #000000;
}
.custom-copy-button:hover {
  color: #000000;
  background-color: #ffc700;
  border: solid 2px #000000;
}
.custom-info-button {
  justify-content: center;
  color: #ffc700;
  padding: 10px 20px;
  background-color: #000000;
  border: solid 2px #ffc700;
  cursor: pointer;
  z-index: 10000;
  display: block;
  text-align: center;
  letter-spacing: 2px;
  transition: all 0.3s ease;
  height: auto;
  max-width: 400px;
}
.custom-info-button:hover {
  color: #000000;
  background-color: #ffc700;
  border: solid 2px #000000;
}
.custom-info-button.shown:hover {
  color: #ffc700;
  background-color: #000000;
  border: solid 2px #ffc700;
  cursor: default;
}
//lightbox
.custom-lightbox {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.95);
    justify-content: center;
    align-items: center;
    z-index: 9999;
}
.custom-lightbox-content {
   max-width: 95%;
   max-height: 95%;
   cursor: pointer;
   transition: transform 0.3s ease;
   transform-origin: center center;
}
.custom-lightbox-content.zoomed {
    cursor: zoom-out;
    transform: scale(2);
}
.custom-lightbox-placeholder-container {
    width: 100%;
    height: 100%;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
}
.lds-heart .custom-lightbox-placeholder {
  color: #ffc700
}
.custom-navigation {
    position: absolute;
    top: 50%;
    width: 95%;
    display: flex;
    justify-content: space-between;
    padding: 0 10px;
}
.custom-prev, .custom-next {
   background-color: #000000;
   border: solid 2px #ffc700;
   color: #ffc700;
   font-size: 24px;
   line-height: 40px;
   width: 40px;
   height: 40px;
   text-align: center;
   cursor: pointer;
   transition: all 0.3s ease;
   z-index: 99999;
}
.custom-prev:hover, .custom-next:hover {
    background-color: #ffc700;
    color: #000000; 
  	border-color: #000000;
}
Previous
Previous

Comments and rating for shop products with firebase (Part 1)

Next
Next

User account page with firebase