From 15df6f2608dc18a2707e5f9fb694c2766fd0dab1 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 10 Oct 2026 15:23:43 -0300 Subject: i hate vibecoding but i hate javascript more --- scripts/justifiedgallery.js | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/scripts/justifiedgallery.js b/scripts/justifiedgallery.js index 0d8c2e1..690bd49 100644 --- a/scripts/justifiedgallery.js +++ b/scripts/justifiedgallery.js @@ -8,7 +8,8 @@ rowHeight: 80, //target row height (px) margins: 2, //gap between images (px) maxRowHeight: '150%', //cap on stretched row height - lastRow: 'nojustify' //how to handle the final incomplete row + lastRow: 'nojustify', //how to handle the final incomplete row + trickleMs: 1000 //before any scroll: load 1 image per this many ms }; const galleryEl = document.getElementById('gallery'); @@ -55,6 +56,37 @@ const res = await fetch(CONFIG.manifest); const items = await res.json(); + const pending = []; // imgs still waiting for a src, in order + let unlocked = false; + + function startLoad(img) { + const src = img.dataset.src; + if (!src) return; + delete img.dataset.src; + const reveal = () => img.parentNode.classList.add('jg-entry-visible'); + img.addEventListener('load', reveal, { once: true }); + img.addEventListener('error', reveal, { once: true }); + img.src = src; + } + + // before the first scroll: 1 image per CONFIG.trickleMs + const trickle = setInterval(() => { + const img = pending.shift(); + if (img) startLoad(img); + else clearInterval(trickle); + }, CONFIG.trickleMs); + + // first scroll/wheel/touch anywhere (page or gallery box): release everything. + // img.loading='lazy' still makes the browser only fetch what's near the viewport. + function unlock() { + if (unlocked) return; + unlocked = true; + clearInterval(trickle); + pending.splice(0).forEach(startLoad); + } + ['scroll', 'wheel', 'touchmove'].forEach(ev => + window.addEventListener(ev, unlock, { passive: true, capture: true, once: true })); + items.forEach(({ folder, file, width, height }) => { const src = folder + file; const thumbSrc = CONFIG.thumbFolder + folder + file.replace(CONFIG.imageExtensions, '.webp'); @@ -67,7 +99,8 @@ img.loading = 'lazy'; img.width = width; img.height = height; - img.src = thumbSrc; + img.dataset.src = thumbSrc; // real src is set later (trickle / first scroll) + pending.push(img); entry.appendChild(img); entry.addEventListener('click', async () => { @@ -83,7 +116,8 @@ rowHeight: CONFIG.rowHeight, margins: CONFIG.margins, maxRowHeight: CONFIG.maxRowHeight, - lastRow: CONFIG.lastRow + lastRow: CONFIG.lastRow, + waitThumbnailsLoad: false // lay out from width/height attrs, no src needed }); } @@ -322,6 +356,7 @@ const justifiedGallery = (function () { captionFromEntry(entry) { const captions = Array.from(entry.querySelectorAll('.jg-caption')); + return captions.find(el => el.parentNode === entry) || null; } -- cgit v1.3-2-g0d8e