// Justified photo grid layout (Google Photos style) // Inspired by https://github.com/flickr/justified-layout (function () { const SPACING = 3; const PADDING = 3; // Photos that all share the same aspect ratio give rows of identical height, // which reads as a contact sheet. Some photos are therefore displayed with a // slightly different ratio than their own: `object-fit: cover` trims the // edges, and the justified rows become irregular on their own. const CROP_STEPS = [0.8, 1, 1, 1.25]; // display ratio multipliers, drawn per photo // id -> multiplier, drawn once per page load and kept on resize (and when a // photo is deleted) so the gallery does not reshuffle while it is looked at. const cropPlan = new Map(); function displayRatio(item, index) { const w = parseInt(item.dataset.width) || 1; const h = parseInt(item.dataset.height) || 1; const id = item.dataset.slideName || 'i' + index; if (!cropPlan.has(id)) { cropPlan.set(id, CROP_STEPS[Math.floor(Math.random() * CROP_STEPS.length)]); } return (w / h) * cropPlan.get(id); } function applyLayout() { const container = document.getElementById('lightgallery'); if (!container) return; const items = [...container.querySelectorAll('.photo-item')]; if (!items.length) return; const containerWidth = container.offsetWidth - PADDING * 2; const TARGET_HEIGHT = Math.min(220, Math.floor(containerWidth * 0.4)); const ratios = items.map(displayRatio); // Group items into rows const rows = []; let rowStart = 0; while (rowStart < ratios.length) { let sum = 0; let end = rowStart; while (end < ratios.length) { sum += ratios[end]; const rowWidth = sum * TARGET_HEIGHT + SPACING * (end - rowStart); end++; if (rowWidth >= containerWidth) break; } rows.push({ start: rowStart, end }); rowStart = end; } // Position each item let top = PADDING; rows.forEach(({ start, end }, rowIndex) => { const isLastRow = rowIndex === rows.length - 1; const count = end - start; const sumRatios = ratios.slice(start, end).reduce((a, b) => a + b, 0); const totalSpacing = SPACING * (count - 1); // Fill the container width, except for a last row that isn't full: // it keeps the target height, but never wider than the container. const fitHeight = (containerWidth - totalSpacing) / sumRatios; const rowHeight = isLastRow ? Math.min(TARGET_HEIGHT, fitHeight) : fitHeight; let left = PADDING; for (let i = start; i < end; i++) { const width = Math.round(ratios[i] * rowHeight); const height = Math.round(rowHeight); const item = items[i]; item.style.top = top + 'px'; item.style.left = left + 'px'; item.style.width = width + 'px'; item.style.height = height + 'px'; left += width + SPACING; } top += Math.round(rowHeight) + SPACING; }); container.style.height = (top - SPACING + PADDING) + 'px'; items.forEach(item => { item.style.visibility = 'visible'; }); } window.applyJustifiedLayout = applyLayout; document.addEventListener('DOMContentLoaded', applyLayout); window.addEventListener('resize', applyLayout); })();