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 .= '';