Rework lazy loading as js controlled fetches

This commit is contained in:
2026-01-28 13:20:35 +01:00
parent 80e3aa95d8
commit e6d6ad3c7b
2 changed files with 95 additions and 11 deletions

View File

@@ -8,15 +8,101 @@
// ============================================================ // ============================================================
// Gallery Thumbnails Module // Gallery Thumbnails Module
// ============================================================ // ============================================================
// Thumbnail loading now relies on native loading="lazy" attribute. // Uses fetch() with AbortController to load thumbnails.
// The browser handles deferred loading, connection limits, and // This allows true HTTP request cancellation on navigation,
// automatic cancellation on navigation. // unlike native loading="lazy" where queued requests block.
//
// This module is kept as a stub for potential future enhancements.
Luxtools.GalleryThumbnails = (function () { 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() { function init() {
// Native lazy loading handles everything. var imgs = document.querySelectorAll(
// No JavaScript intervention needed. '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 { return {

View File

@@ -88,7 +88,7 @@ class Output
$caption = $label; $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, [ $thumbUrl = $this->withQueryParams($url, [
'thumb' => 1, 'thumb' => 1,
'w' => $genThumbW, 'w' => $genThumbW,
@@ -107,12 +107,10 @@ class Output
. '>'; . '>';
$renderer->doc .= '<img' $renderer->doc .= '<img'
. ' class="luxtools-thumb"' . ' class="luxtools-thumb"'
. ' src="' . $thumbSrc . '"' . ' data-src="' . $thumbSrc . '"'
. ' alt=""' . ' alt=""'
. ' width="' . $thumbW . '"' . ' width="' . $thumbW . '"'
. ' height="' . $thumbH . '"' . ' height="' . $thumbH . '"'
. ' loading="lazy"'
. ' decoding="async"'
. ' />'; . ' />';
$renderer->doc .= '<span class="luxtools-gallery-caption">' . $caption . '</span>'; $renderer->doc .= '<span class="luxtools-gallery-caption">' . $caption . '</span>';
$renderer->doc .= '</a>'; $renderer->doc .= '</a>';