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