improved image loading animation
All checks were successful
Run Integration Tests / test (push) Successful in 28s
All checks were successful
Run Integration Tests / test (push) Successful in 28s
This commit is contained in:
parent
5fdbe231d1
commit
30528d629b
4 changed files with 111 additions and 21 deletions
63
static/css/style-imageloading.css
Normal file
63
static/css/style-imageloading.css
Normal 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 */
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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"></span>
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue