diff --git a/static/css/style-imageloading.css b/static/css/style-imageloading.css new file mode 100644 index 0000000..5d3b961 --- /dev/null +++ b/static/css/style-imageloading.css @@ -0,0 +1,63 @@ +/* Image Loading Effect */ +.loading-image { + position: relative; + overflow: hidden; + background-color: var(--snip-background); + background-image: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 25%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0) 75% + ); + background-size: 200% 100%; + animation: image-wave 2s infinite linear; +} + +/* Title Loading Effect */ +.title-loading { + position: relative; + overflow: hidden; + color: transparent !important; + background-color: var(--snip-background); + min-height: 1.2em; + width: 80%; + margin: 0 auto; + top: 2px; + border-radius: 6px; +} + +.title-loading::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent 25%, + rgba(255, 255, 255, 0.25) 50%, + transparent 75% + ); + background-size: 200% 100%; + animation: title-wave 2.5s infinite linear; +} + +/* Animations */ +@keyframes image-wave { + 0% { + background-position: -100% 0; /* Start off-screen left */ + } + 100% { + background-position: 100% 0; /* End off-screen right */ + } +} + +@keyframes title-wave { + 0% { + background-position: -100% 0; /* Start off-screen left */ + } + 100% { + background-position: 100% 0; /* End off-screen right */ + } +} diff --git a/static/js/dynamicscrollingimages.js b/static/js/dynamicscrollingimages.js index d6eb3e7..536db43 100644 --- a/static/js/dynamicscrollingimages.js +++ b/static/js/dynamicscrollingimages.js @@ -1,5 +1,40 @@ +// dynamicscrollingimages.js (function() { - // Configuration + // Add loading effects to image and title + function addLoadingEffects(imgElement) { + const title = imgElement.closest('.image').querySelector('.img_title'); + imgElement.classList.add('loading-image'); + title.classList.add('title-loading'); + } + + function removeLoadingEffects(imgElement) { + const title = imgElement.closest('.image').querySelector('.img_title'); + imgElement.classList.remove('loading-image'); + title.classList.remove('title-loading'); + + if (imgElement.src.endsWith('/images/missing.svg')) { + imgElement.closest('.image').remove(); + } + } + + // Modified handleImageError with theme-consistent error handling + function handleImageError(imgElement, retryCount = 3, retryDelay = 1000) { + const container = imgElement.closest('.image'); + const title = container.querySelector('.img_title'); + + if (retryCount > 0) { + setTimeout(() => { + imgElement.src = imgElement.getAttribute('data-full'); + imgElement.onerror = () => handleImageError(imgElement, retryCount - 1, retryDelay); + }, retryDelay); + } else { + imgElement.classList.remove('loading-image'); + title.classList.remove('title-loading'); + container.style.display = 'none'; + } + } + + // Rest of your existing code with minor additions const imageStatusInterval = 500; const scrollThreshold = 500; const loadingIndicator = document.getElementById('message-bottom-left'); @@ -14,18 +49,6 @@ let imageIds = []; let imageStatusTimer; - function handleImageError(imgElement, retryCount = 3, retryDelay = 1000) { - if (retryCount > 0) { - setTimeout(() => { - imgElement.src = imgElement.getAttribute('data-full'); - imgElement.onerror = () => handleImageError(imgElement, retryCount - 1, retryDelay); - }, retryDelay); - } else { - console.warn('Image failed to load:', imgElement.getAttribute('data-full')); - imgElement.parentElement.style.display = 'none'; - } - } - function ensureScrollable() { if (noMoreImages) return; if (document.body.scrollHeight <= window.innerHeight) { @@ -59,21 +82,21 @@ let img = clonedImageDiv.querySelector('img'); if (img && img.getAttribute('data-id')) { + addLoadingEffects(img); if (hardCacheEnabled) { - img.src = '/static/images/placeholder.svg'; + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; img.onerror = () => handleImageError(img); imageElements.push(img); imageIds.push(img.getAttribute('data-id')); } else { img.src = img.getAttribute('data-full'); + img.onload = () => removeLoadingEffects(img); img.onerror = () => handleImageError(img); } } }); - if (hardCacheEnabled) { - checkImageStatus(); // Immediately check status for new images - } + if (hardCacheEnabled) checkImageStatus(); ensureScrollable(); } else { noMoreImages = true; @@ -101,6 +124,7 @@ const id = img.getAttribute('data-id'); if (statusMap[id]) { img.src = statusMap[id]; + img.onload = () => removeLoadingEffects(img); img.onerror = () => handleImageError(img); } else { pendingImages.push(img); @@ -117,21 +141,24 @@ }); } - // Initialize + // Initialize with loading effects document.querySelectorAll('img[data-id]').forEach(img => { const id = img.getAttribute('data-id'); if (id) { + addLoadingEffects(img); imageElements.push(img); imageIds.push(id); if (hardCacheEnabled) { - img.src = '/static/images/placeholder.svg'; + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; } else { img.src = img.getAttribute('data-full'); + img.onload = () => removeLoadingEffects(img); } img.onerror = () => handleImageError(img); } }); + // Rest of your existing code remains unchanged if (hardCacheEnabled) { imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval); checkImageStatus(); @@ -145,7 +172,6 @@ } }); - // Cleanup window.addEventListener('beforeunload', () => { if (imageStatusTimer) clearInterval(imageStatusTimer); }); diff --git a/static/js/imageviewer.js b/static/js/imageviewer.js index 6a328cb..e04b350 100644 --- a/static/js/imageviewer.js +++ b/static/js/imageviewer.js @@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded', function() {