Add a lightbox effect for images in html
First, just a reference how to add images in .org files - images in org files.
1 css
div.lightbox { display: flex; flex-direction: row; overflow-x: scroll; background-color: black; padding: 1px 5px 1px 5px; box-shadow: 0 0 1px 1px black; scrollbar-color: #444 black; scrollbar-width: none; width: calc(100vw - 10px); margin-left: calc(50% - 50vw + 5px); } div.lightbox img:not(.lightbox) { flex: 0 0 auto; height: calc(100% - 4px); margin: 2px; } div.lightbox figure:not(.lightbox) { flex: 0 0 auto; height: calc(100% - 4px); margin: 2px; } div.lightbox figure:not(.lightbox) img { height: calc(100% - 16pt); margin: 0; } div.lightbox figure:not(.lightbox) figcaption { font-size: 8pt; color: white; text-align: center; } figure.lightbox { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); margin: 0; overflow: hidden; } figure.lightbox img.lightbox { position: absolute; } a figure.lightbox figcaption { max-width: 90%; position: absolute; left: 50%; transform: translate(-50%, 0); bottom: 5px; color: white; text-shadow: black 0 0 10px; }
2 js
// copied from arvydasg.github.io/static/lightbox.js let isDragging = false; let wasDragged = false; let touchCoordinate = []; let lightboxTime = undefined; document.addEventListener('keyup', e => { let lightboxImage = document.querySelector('figure.lightbox img.lightbox'); if (e.key === 'Escape' && lightboxImage) { let figure = lightboxImage.parentNode; exitLightbox(figure, lightboxImage); } }); function openLightbox(event) { if (event.buttons !== 0) { return; } // if the browser issues both click and touch events, ignore // the later one: if (event.timeStamp - lightboxTime < 100) { return; } lightboxTime = event.timeStamp; let image = event.target; let figure = image.parentNode; if (wasDragged === true) { wasDragged = false; } else if (figure.classList.contains('lightbox')) { exitLightbox(figure, image); } else { enterLightbox(figure, image); } } function enterLightbox(figure, image) { // fix body in place so it doesn't scroll when the lightbox is // moved by touch document.body.style['top'] = `${-window.scrollY}px`; document.body.style['width'] = `${window.innerWidth}px`; document.body.style['position'] = 'fixed'; // add fake figure as a placeholder while lightbox is showing to // prevent relayout let fakeFig = document.createElement('figure'); fakeFig.id = 'fakefig'; fakeFig.style['width'] = `${figure.offsetWidth}px`; fakeFig.style['height'] = `${figure.offsetHeight}px`; fakeFig.style['background-color'] = '#f0f0f0'; figure.parentNode.insertBefore(fakeFig, figure); // activate lightbox figure.classList.add('lightbox'); image.classList.add('lightbox'); image.onwheel = zoomLightbox; image.style['max-width'] = '90%'; image.style['max-height'] = '90%'; image.addEventListener('mousedown', lightboxMouseDown); image.addEventListener('mousemove', lightboxMouseMove); image.addEventListener('mouseup', lightboxMouseUp); image.setAttribute('draggable', false); // replace thumbnail with full-resolution image (if necessary): if (image.src.includes('thumb')) { image.src = image.src.replace('thumb.', ''); // reposition to center once image is loaded (it will move // because its size changes) image.onload = e => { image.style['left'] = `${(window.innerWidth-image.offsetWidth)/2}px`; image.style['top'] = `${(window.innerHeight-image.offsetHeight)/2}px`; image.onload = undefined; } } // set initial image position to center: image.style['left'] = `${(window.innerWidth-image.offsetWidth)/2}px`; image.style['top'] = `${(window.innerHeight-image.offsetHeight)/2}px`; // hide all other images in this figure: for (let otherImage of figure.querySelectorAll('img:not(.lightbox)')) { otherImage.style['visibility'] = 'hidden'; } } function exitLightbox(figure, image) { // release body let scrollY = parseInt(document.body.style['top']); document.body.style['top'] = ''; document.body.style['position'] = ''; document.body.style['width'] = ''; window.scrollTo(0, -scrollY); // remove fake figure let fakeFig = document.getElementById('fakefig'); fakeFig.remove(); // disable lightbox figure.classList.remove('lightbox'); image.classList.remove('lightbox'); image.onwheel = undefined; image.style['max-width'] = ''; image.style['max-height'] = ''; image.style['top'] = ''; image.style['left'] = ''; image.removeEventListener('mousedown', lightboxMouseDown); image.removeEventListener('mousemove', lightboxMouseMove); image.removeEventListener('mouseup', lightboxMouseUp); image.setAttribute('draggable', true); // unhide all images in this figure: for (let otherImage of figure.querySelectorAll('img')) { otherImage.style['visibility'] = ''; } } function lightboxMouseDown(event) { isDragging = true; } function lightboxMouseMove(event) { if (isDragging === true) { let image = event.target; image.style['left'] = `${image.offsetLeft + event.movementX}px`; image.style['top'] = `${image.offsetTop + event.movementY}px`; moveImageIntoBorders(image); // prevent closing of figure: if (event.movementX != 0 || event.movementY != 0) { wasDragged = true; } } } function lightboxMouseUp(event) { isDragging = false; } function zoomLightbox(event) { let image = event.target; // relative position on image: let imageRect = image.getBoundingClientRect(); let imageX = (event.clientX-imageRect.left)/imageRect.width; let imageY = (event.clientY-imageRect.top)/imageRect.height; // zoom in: let zoomFactor = imageRect.width / window.innerWidth; zoomFactor /= 1.0 - event.wheelDeltaY / 360; zoomFactor = Math.min(Math.max(zoomFactor, 0.9), 5); image.style['max-width'] = `${zoomFactor*100}%`; image.style['max-height'] = `${zoomFactor*100}%`; // pan so the image does not move under cursor: let newPositionX = -imageX*image.offsetWidth + event.clientX; let newPositionY = -imageY*image.offsetHeight + event.clientY; image.style['left'] = `${newPositionX}px`; image.style['top'] = `${newPositionY}px`; moveImageIntoBorders(image); // do not scroll background event.preventDefault(); } function moveImageIntoBorders(image) { // make sure the image stays within the viewport borders: imageRect = image.getBoundingClientRect(); // refresh to new coordinates if (imageRect.width <= window.innerWidth*0.9) { // image fits into figure: prevent edges from leaving figure if (imageRect.left < window.innerWidth*0.05) { image.style['left'] = `${window.innerWidth*0.05}px`; } else if (imageRect.right > window.innerWidth*0.95) { image.style['left'] = `${window.innerWidth*0.95-imageRect.width}px`; } } else { // image too big for figure: prevent edges from entering figure if (imageRect.left > window.innerWidth*0.05) { image.style['left'] = `${window.innerWidth*0.05}px`; } else if (imageRect.right < window.innerWidth*0.95) { image.style['left'] = `${window.innerWidth*0.95-imageRect.width}px`; } } if (imageRect.height <= window.innerHeight*0.9) { // image fits into figure: prevent edges from leaving figure if (imageRect.top < window.innerHeight*0.05) { image.style['top'] = `${window.innerHeight*0.05}px`; } else if (imageRect.bottom > window.innerHeight*0.95) { image.style['top'] = `${window.innerHeight*0.95-imageRect.height}px`; } } else { // image too big for figure: prevent edges from entering figure if (imageRect.top > window.innerHeight*0.05 ) { image.style['top'] = `${window.innerHeight*0.05}px`; } else if (imageRect.bottom < window.innerHeight*0.95) { image.style['top'] = `${window.innerHeight*0.95-imageRect.height}px`; } } } // these function implements a poor-man's 'onclick' for touch events: function handleTouchStart(event) { if (event.touches.length == 1 && touchCoordinate.length == 0) { // remember where the touch started: touchCoordinate = [event.touches[0].screenX, event.touches[0].screenY]; } } function handleTouchMove(event) { // if the cursor moves too much during the touch, it's not a click: if (event.touches.length == 1 && touchCoordinate.length == 2) { if ((Math.abs(event.touches[0].screenX - touchCoordinate[0]) > 10) || (Math.abs(event.touches[0].screenY - touchCoordinate[1]) > 10)) { touchCoordinate = []; } // if more than one finger touches the screen, it's not a click: } else if (event.touches.length != 1) { touchCoordinate = []; } } function handleTouchEnd(event) { // if touchCoordinate still exists at touch end, it's a click: if (touchCoordinate.length == 2) { // if the browser issues both click and touch events, ignore // the later one: if (event.timeStamp - lightboxTime < 100) { return; } lightboxTime = event.timeStamp; let image = event.target; let figure = image.parentNode; if (figure.classList.contains('lightbox')) { exitLightbox(figure, image); } else { enterLightbox(figure, image); } touchCoordinate = []; } } function handleTouchCancel(event) { // if the touch is cancelled, it's not a click: touchCoordinate = []; } window.addEventListener('DOMContentLoaded', (event) => { var figures = document.getElementsByTagName('figure'); for (let figure of figures) { let images = figure.querySelectorAll('img'); for (let image of images) { image.onclick = openLightbox; // This is a workaround for image.ontap = opanLightbox: image.addEventListener("touchstart", handleTouchStart); image.addEventListener("touchend", handleTouchEnd); image.addEventListener("touchmove", handleTouchMove); image.addEventListener("touchcancel", handleTouchCancel); } }});