diff --git a/js/gallery-thumbnails.js b/js/gallery-thumbnails.js index e532266..7d48f1e 100644 --- a/js/gallery-thumbnails.js +++ b/js/gallery-thumbnails.js @@ -8,15 +8,101 @@ // ============================================================ // Gallery Thumbnails Module // ============================================================ - // Thumbnail loading now relies on native loading="lazy" attribute. - // The browser handles deferred loading, connection limits, and - // automatic cancellation on navigation. - // - // This module is kept as a stub for potential future enhancements. + // Uses fetch() with AbortController to load thumbnails. + // This allows true HTTP request cancellation on navigation, + // unlike native loading="lazy" where queued requests block. Luxtools.GalleryThumbnails = (function () { + var controller = null; + var maxConcurrent = 4; + var activeCount = 0; + var queue = []; + + function abortAll() { + if (controller) { + controller.abort(); + controller = null; + } + queue = []; + activeCount = 0; + } + + function processQueue() { + if (!controller) return; + while (activeCount < maxConcurrent && queue.length > 0) { + var img = queue.shift(); + loadThumb(img); + } + } + + function loadThumb(img) { + if (!controller) return; + + var src = img.getAttribute('data-src'); + if (!src) { + processQueue(); + return; + } + + activeCount++; + + fetch(src, { signal: controller.signal }) + .then(function (response) { + if (!response.ok) throw new Error('HTTP ' + response.status); + return response.blob(); + }) + .then(function (blob) { + img.src = URL.createObjectURL(blob); + img.removeAttribute('data-src'); + }) + .catch(function (err) { + // Aborted or failed - ignore + if (err.name !== 'AbortError') { + // Keep data-src for potential retry, just log + } + }) + .finally(function () { + activeCount--; + processQueue(); + }); + } + + function queueThumb(img) { + if (!controller) return; + if (!img.getAttribute('data-src')) return; + if (img.getAttribute('data-queued') === '1') return; + img.setAttribute('data-queued', '1'); + queue.push(img); + processQueue(); + } + function init() { - // Native lazy loading handles everything. - // No JavaScript intervention needed. + var imgs = document.querySelectorAll( + 'div.luxtools-gallery img.luxtools-thumb[data-src], div.luxtools-imagebox img[data-src]' + ); + if (!imgs || !imgs.length) return; + + // Create abort controller for all requests + controller = new AbortController(); + + // Abort all pending requests on navigation + window.addEventListener('beforeunload', abortAll); + window.addEventListener('pagehide', abortAll); + + // Use IntersectionObserver to trigger loading + if ('IntersectionObserver' in window) { + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + queueThumb(entry.target); + io.unobserve(entry.target); + }); + }, { rootMargin: '200px' }); + + imgs.forEach(function (img) { io.observe(img); }); + } else { + // Fallback: queue all + imgs.forEach(queueThumb); + } } return { diff --git a/src/Output.php b/src/Output.php index d96e61a..79cb346 100644 --- a/src/Output.php +++ b/src/Output.php @@ -88,7 +88,7 @@ class Output $caption = $label; } - // Build thumbnail URL - rely on native loading="lazy" for deferred loading + // Build thumbnail URL - JavaScript will load via fetch() for cancellability $thumbUrl = $this->withQueryParams($url, [ 'thumb' => 1, 'w' => $genThumbW, @@ -107,12 +107,10 @@ class Output . '>'; $renderer->doc .= ''; $renderer->doc .= '' . $caption . ''; $renderer->doc .= '';