change grid rendering

This commit is contained in:
2026-07-14 20:45:33 +02:00
parent d92004ba3a
commit e05b60cee8
2 changed files with 56 additions and 53 deletions

View File

@@ -7098,20 +7098,43 @@ button#togglebg {
animation: none;
}
/* ─── Posts Grid: no skeleton on grid thumbnails — plain dark bg while loading ─────── */
/* ─── Posts Grid Skeleton: always visible shimmer while image loads ──────────────────
The <a.thumb> is always visible — skeleton shimmer shows immediately.
Once the image is loaded (.loaded class added), the shimmer is removed and
the background-image (set via --thumb-bg) is shown. Children stay on top
via their own stacking context. */
/* Skeleton shimmer on unloaded thumbs — always visible */
div.posts>a.lazy-thumb:not(.loaded) {
background: rgba(255, 255, 255, 0.04);
background: linear-gradient(90deg,
rgba(255,255,255,0.04) 0%,
rgba(255,255,255,0.09) 45%,
rgba(255,255,255,0.04) 90%);
background-size: 300% 100%;
animation: thumb-shimmer 1.8s ease-in-out infinite;
}
/* Smooth GPU-composited fade-in when thumbnail image finishes loading.
opacity transition is always compositor-promoted — no paint cost. */
div.posts>a.thumb {
opacity: 0;
transition: opacity 0.25s ease-out;
@keyframes thumb-shimmer {
0% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Once loaded: show the real image and fade in smoothly */
div.posts>a.thumb.loaded {
opacity: 1;
background-image: var(--thumb-bg);
animation: thumb-reveal 0.3s ease-out both;
}
@keyframes thumb-reveal {
from { opacity: 0.4; }
to { opacity: 1; }
}
/* Ensure child elements (indicators, labels) are always visible above background */
div.posts>a.thumb > *,
div.posts>a.thumb .thumb-indicators {
position: relative;
z-index: 1;
}

View File

@@ -118,24 +118,15 @@ window.cancelAnimFrame = (function () {
// Clear grid cache to force fresh render on next navigation
if (typeof gridCacheMap !== 'undefined') gridCacheMap.clear();
// Update elements with data-bg (grid items).
// We look for any data-bg or inline style containing the thumbnail path for this ID.
document.querySelectorAll(`[data-bg*="/t/${idStr}.webp"], [data-bg*="/t/${idStr}_blur.webp"], [style*="/t/${idStr}.webp"], [style*="/t/${idStr}_blur.webp"]`).forEach(el => {
// If it has data-bg, update it (this handles lazy-thumb logic)
document.querySelectorAll(`[data-bg*="/t/${idStr}.webp"], [data-bg*="/t/${idStr}_blur.webp"]`).forEach(el => {
if (el.dataset.bg) {
el.dataset.bg = window.applyThumbCacheBust(el.dataset.bg);
}
// If it's already showing the background, update the style directly
if (el.style.backgroundImage || el.getAttribute('style')?.includes('background-image')) {
const currentStyle = el.getAttribute('style') || '';
// Match url(...) contents
const newStyle = currentStyle.replace(/url\(['"]?([^'"]+)['"]?\)/g, (match, p1) => {
if (p1.includes(`/t/${idStr}.webp`) || p1.includes(`/t/${idStr}_blur.webp`)) {
return `url('${window.applyThumbCacheBust(p1)}')`;
}
return match;
});
el.setAttribute('style', newStyle);
// Update the --thumb-bg CSS custom property used by the ::after pseudo-element
const currentBg = el.style.getPropertyValue('--thumb-bg');
if (currentBg && (currentBg.includes(`/t/${idStr}.webp`) || currentBg.includes(`/t/${idStr}_blur.webp`))) {
const newUrl = currentBg.replace(/url\(['"](.*?)['"]\)/, (_, p1) => `url('${window.applyThumbCacheBust(p1)}')`);
el.style.setProperty('--thumb-bg', newUrl);
}
});
@@ -180,24 +171,26 @@ window.cancelAnimFrame = (function () {
return window.applyThumbCacheBust(bg);
};
// Helper: apply thumb image via CSS custom property — picked up by the loaded CSS rule
const applyThumb = (thumb, finalBg) => {
thumb.style.setProperty('--thumb-bg', `url('${finalBg}')`);
thumb.classList.add('loaded');
thumb.classList.remove('lazy-thumb');
window.loadedThumbs.add(finalBg);
};
// Synchronously resolve any already-loaded thumbnails before observer starts
document.querySelectorAll('.lazy-thumb').forEach(thumb => {
const finalBg = getFinalBg(thumb);
if (finalBg && window.loadedThumbs.has(finalBg)) {
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.classList.add('loaded');
thumb.classList.remove('lazy-thumb');
applyThumb(thumb, finalBg);
}
});
if (!('IntersectionObserver' in window)) {
document.querySelectorAll('.lazy-thumb').forEach(thumb => {
const finalBg = getFinalBg(thumb);
if (finalBg) {
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.classList.remove('lazy-thumb');
window.loadedThumbs.add(finalBg);
}
if (finalBg) applyThumb(thumb, finalBg);
});
return;
}
@@ -207,36 +200,24 @@ window.cancelAnimFrame = (function () {
entries.forEach(entry => {
if (entry.isIntersecting) {
const thumb = entry.target;
let bg = thumb.dataset.bg;
if (bg && !thumb.classList.contains('loaded')) {
if (thumb.dataset.bg && !thumb.classList.contains('loaded')) {
const finalBg = getFinalBg(thumb);
if (finalBg) {
const img = new Image();
img.src = finalBg;
if (img.complete && img.naturalWidth > 0) {
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.classList.add('loaded');
thumb.classList.remove('lazy-thumb');
window.loadedThumbs.add(finalBg);
applyThumb(thumb, finalBg);
} else {
img.onload = () => {
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.classList.add('loaded');
thumb.classList.remove('lazy-thumb');
window.loadedThumbs.add(finalBg);
};
img.onload = () => applyThumb(thumb, finalBg);
img.onerror = () => {
const retries = parseInt(thumb.dataset.retries || '0');
if (retries < 3) {
thumb.dataset.retries = retries + 1;
setTimeout(() => {
img.src = finalBg + '?r=' + Date.now();
}, 1000);
setTimeout(() => { img.src = finalBg + '?r=' + Date.now(); }, 1000);
} else {
// All retries exhausted — show fallback for audio items
const mime = thumb.dataset.mime || '';
if (mime.startsWith('audio/')) {
thumb.style.backgroundImage = `url('/s/img/audio.webp')`;
thumb.style.setProperty('--thumb-bg', `url('/s/img/audio.webp')`);
thumb.classList.add('thumb-fallback');
}
thumb.classList.remove('lazy-thumb');
@@ -256,7 +237,6 @@ window.cancelAnimFrame = (function () {
window._lazyVisibilityBound = true;
document.addEventListener('visibilitychange', () => {
if (!document.hidden && typeof window.initLazyLoading === 'function') {
// Clear observation state for pending items to force re-observation
document.querySelectorAll('.lazy-thumb:not(.loaded)').forEach(t => {
delete t.dataset.lazyObserved;
});
@@ -7710,8 +7690,8 @@ class NotificationSystem {
thumb.dataset.mode = mode;
thumb.dataset.bg = `/t/${data.id}.webp`;
thumb.dataset.size = '1'; // New items start with no contributions → tier 1
thumb.style.backgroundImage = `url('/t/${data.id}.webp')`;
thumb.style.opacity = '0';
thumb.style.setProperty('--thumb-bg', `url('/t/${data.id}.webp')`);
thumb.classList.add('loaded');
thumb.style.transform = 'scale(0.9)';
thumb.innerHTML = '<p></p>' + (data.is_oc ? '<span class="oc-indicator anim">OC</span>' : '');
@@ -10144,7 +10124,7 @@ document.addEventListener('click', (e) => {
if (baseBg && shouldBlurThis) {
const finalBg = window.applyThumbCacheBust(baseBg.replace('.webp', '_blur.webp'));
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.style.setProperty('--thumb-bg', `url('${finalBg}')`);
}
}
return;
@@ -10171,7 +10151,7 @@ document.addEventListener('click', (e) => {
const baseBg = thumb.dataset.bg;
if (baseBg && shouldBlurThis) {
const finalBg = window.applyThumbCacheBust(baseBg);
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.style.setProperty('--thumb-bg', `url('${finalBg}')`);
}
// Dynamically inject the hide-again eye-slash button inside <p>