improved image loading animation
All checks were successful
Run Integration Tests / test (push) Successful in 28s

This commit is contained in:
partisan 2025-04-23 12:26:05 +02:00
parent 5fdbe231d1
commit 30528d629b
4 changed files with 111 additions and 21 deletions

View file

@ -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 */
}
}

View file

@ -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);
});

View file

@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
<p class="image-alt" id="viewer-title"></p>
<br>
<div class="search-type-icons" style="display:flex; justify-content:center; gap:15px; flex-wrap: wrap;">
<div class="search-type-icons" style="display:flex; justify-content:center; flex-wrap: wrap;">
<div class="icon-button">
<button class="material-icons-round clickable btn-nostyle" id="viewer-copy-link">
<span class="material-icons-round">&#xe37c;</span>

View file

@ -15,6 +15,7 @@
}
</style>
</noscript>
<link rel="stylesheet" href="/static/css/style-imageloading.css">
<link rel="stylesheet" href="/static/css/style-imageviewer.css">
<link rel="stylesheet" href="/static/css/style-fixedwidth.css">
<link rel="stylesheet" href="/static/css/style-menu.css">