jhfg
This commit is contained in:
@@ -7166,25 +7166,44 @@ button#togglebg {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
/* ─── Posts Grid Skeleton: shimmer on real items while thumbnail loads ─────── */
|
||||
/* ─── Posts Grid Skeleton: GPU-composited shimmer while thumbnail loads ───── */
|
||||
/* Uses ::before + transform:translateX instead of background-position. */
|
||||
/* background-position forces a CPU repaint every frame for every element; */
|
||||
/* transform runs on the compositor thread — no main-thread blocking. */
|
||||
div.posts>a.lazy-thumb:not(.loaded) {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.posts>a.lazy-thumb:not(.loaded)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 255, 255, 0.04) 0%,
|
||||
rgba(255, 255, 255, 0.10) 40%,
|
||||
rgba(255, 255, 255, 0.04) 80%);
|
||||
background-size: 600px 100%;
|
||||
animation: skeleton-shimmer 1.6s ease-in-out infinite;
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.10) 50%,
|
||||
transparent 100%);
|
||||
transform: translateX(-100%);
|
||||
animation: shimmer-slide 1.6s ease-in-out infinite;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Stagger shimmer phases so items feel alive individually */
|
||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+2) {
|
||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+2)::before {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+3) {
|
||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+3)::before {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes shimmer-slide {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
|
||||
/* Make individual item entry subtler and faster for infinite scroll */
|
||||
@keyframes fadeInFX {
|
||||
|
||||
@@ -2167,12 +2167,6 @@ window.cancelAnimFrame = (function () {
|
||||
|
||||
const currentScroll = window.scrollY;
|
||||
|
||||
// Immediately fade out the current main content to provide a clean slate for the next page
|
||||
if (main) {
|
||||
main.classList.add('grid-transition');
|
||||
main.classList.remove('show');
|
||||
}
|
||||
|
||||
// Save scroll position for the current page before we leave it (only for new navigations)
|
||||
if (!options.skipPush) {
|
||||
const currentState = history.state || {};
|
||||
@@ -2216,84 +2210,79 @@ window.cancelAnimFrame = (function () {
|
||||
}
|
||||
|
||||
|
||||
// Check for cached grid (Index/Grid Only) - RESTORE EARLY to avoid pagination/layout destruction
|
||||
if (isGrid && options.skipPush) {
|
||||
// Check for cached grid — fires on ANY navigation to a grid URL (not just browser back/forward).
|
||||
// The fade-out of main must NOT happen before this check: doing so causes the
|
||||
// IntersectionObserver to fire for every cached thumbnail at once when they re-enter
|
||||
// the viewport, loading them all simultaneously instead of progressively.
|
||||
if (isGrid) {
|
||||
const targetCacheKey = urlObj.pathname + urlObj.search;
|
||||
if (gridCacheMap.has(targetCacheKey)) {
|
||||
const cache = gridCacheMap.get(targetCacheKey);
|
||||
gridCacheMap.delete(targetCacheKey); // Consume cache
|
||||
// Keep cache alive — cleared on mode/filter changes via gridCacheMap.clear()
|
||||
|
||||
if (navbar) navbar.classList.add('pbwork');
|
||||
stopMedia();
|
||||
|
||||
// Instant fade out background canvas (from item view)
|
||||
// Fade out background canvas (item view blur)
|
||||
const canvas = document.getElementById('bg');
|
||||
if (canvas) {
|
||||
canvas.style.transition = 'none'; // Disable transition
|
||||
canvas.style.transition = 'none';
|
||||
canvas.style.opacity = '0';
|
||||
canvas.classList.remove('fader-in');
|
||||
canvas.classList.add('fader-out');
|
||||
|
||||
// Clean up overrides after render
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
canvas.style.transition = '';
|
||||
canvas.style.opacity = '';
|
||||
}, 50);
|
||||
setTimeout(() => { canvas.style.transition = ''; canvas.style.opacity = ''; }, 50);
|
||||
});
|
||||
}
|
||||
|
||||
// Restore DOM
|
||||
if (main) {
|
||||
main.innerHTML = ''; // Clear item view
|
||||
main.innerHTML = '';
|
||||
main.appendChild(cache.node);
|
||||
cache.node.style.display = ''; // Ensure visible
|
||||
cache.node.classList.remove('cached-grid'); // Cleanup
|
||||
cache.node.style.display = '';
|
||||
cache.node.classList.remove('cached-grid');
|
||||
main.className = '';
|
||||
|
||||
// Clear layout-lock state now that we are restoring the grid
|
||||
document.body.classList.remove('legacy-view', 'layout-modern', 'layout-legacy');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.height = '';
|
||||
|
||||
// Restore Pagination HTML
|
||||
const paginationWrapper = document.querySelector('.pagination-wrapper');
|
||||
if (paginationWrapper && cache.pagination) {
|
||||
paginationWrapper.innerHTML = cache.pagination;
|
||||
}
|
||||
|
||||
// Re-attach Infinite Scroll Handler
|
||||
if (paginationWrapper && cache.pagination) paginationWrapper.innerHTML = cache.pagination;
|
||||
const restoredPosts = cache.node.querySelector('.posts');
|
||||
if (restoredPosts && restoredPosts._scrollHandler) {
|
||||
window.addEventListener('scroll', restoredPosts._scrollHandler);
|
||||
// Ensure loading state is reset just in case
|
||||
window.addEventListener('scroll', restoredPosts._scrollHandler, { passive: true });
|
||||
if (restoredPosts._infiniteState) restoredPosts._infiniteState.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore Scroll Position
|
||||
// Update URL bar (only for explicit clicks, not browser back/forward which already did it)
|
||||
if (!options.skipPush) history.pushState({}, '', url);
|
||||
|
||||
// Restore scroll position
|
||||
requestAnimationFrame(() => window.scrollTo(0, cache.scroll));
|
||||
|
||||
// Update Document Title
|
||||
let titleSuffix = '';
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const page = urlParams.get('p') || urlParams.get('page');
|
||||
if (page) titleSuffix = ` - page ${page}`;
|
||||
document.title = `${window.f0ckDomain}${titleSuffix}`;
|
||||
// Update title
|
||||
const urlParamsC = new URLSearchParams(urlObj.search);
|
||||
const pageC = urlParamsC.get('p') || urlParamsC.get('page');
|
||||
document.title = `${window.f0ckDomain}${pageC ? ` - page ${pageC}` : ''}`;
|
||||
|
||||
// Reset navigation state
|
||||
document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = ''); // Restore pagination visibility
|
||||
if (navbar) navbar.classList.remove("pbwork");
|
||||
document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = '');
|
||||
if (navbar) navbar.classList.remove('pbwork');
|
||||
window.updateVisitIndicators();
|
||||
window.initLazyLoading();
|
||||
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
||||
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
||||
// Sync has-notif highlights — on PWA there's no visibilitychange, so poll here
|
||||
window.NotificationSystemInstance?.pollDebounced?.();
|
||||
isNavigating = false;
|
||||
return; // SKIP FETCH
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss — full fetch path. Fade out now (safe: no cached thumbnails in DOM).
|
||||
if (main) {
|
||||
main.classList.add('grid-transition');
|
||||
main.classList.remove('show');
|
||||
}
|
||||
|
||||
|
||||
// Handle transition from Item View or User Profile back to Grid View
|
||||
const isOnProfilePage = main && main.querySelector('.profile_head');
|
||||
@@ -3695,6 +3684,12 @@ window.cancelAnimFrame = (function () {
|
||||
targetUrl = targetUrl.replace(/[?&]strict=1/, '').replace(/[?&]$/, '');
|
||||
}
|
||||
|
||||
// Already on this exact grid page — just scroll to top, no reload
|
||||
if (targetUrl.split('#')[0] === window.location.href.split('#')[0] && !anyLink.hash && document.querySelector('.posts')) {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
const isItemLink = !pathname.match(/\/p\//) && (
|
||||
pathname.match(/^\/\d+/) ||
|
||||
@@ -4689,7 +4684,16 @@ window.cancelAnimFrame = (function () {
|
||||
// Scroll detection - preload before reaching bottom
|
||||
const PRELOAD_OFFSET = 500; // pixels before bottom to trigger load
|
||||
|
||||
// RAF throttle: getBoundingClientRect and offsetHeight force synchronous layout
|
||||
// reflows. Without throttling, Chromium recalculates layout on every scroll
|
||||
// event (potentially hundreds/sec), blocking the compositor and causing freezes.
|
||||
let _scrollTicking = false;
|
||||
const onScroll = () => {
|
||||
if (_scrollTicking) return;
|
||||
_scrollTicking = true;
|
||||
requestAnimationFrame(() => {
|
||||
_scrollTicking = false;
|
||||
|
||||
const currentContainer = postsContainer;
|
||||
// Only run if THIS container is the active one and still in DOM
|
||||
if (!currentContainer || !document.body.contains(currentContainer)) {
|
||||
@@ -4697,12 +4701,12 @@ window.cancelAnimFrame = (function () {
|
||||
return;
|
||||
}
|
||||
if (currentContainer.classList.contains('no-infinite-scroll')) return;
|
||||
if (!document.querySelector('#main')) return;
|
||||
if (!main) return;
|
||||
|
||||
updateUrlAndPagination();
|
||||
|
||||
const scrollPosition = window.innerHeight + window.scrollY;
|
||||
const pageHeight = document.querySelector('#main').offsetHeight;
|
||||
const pageHeight = main.offsetHeight; // use cached ref, not querySelector every scroll
|
||||
const distanceFromBottom = pageHeight - scrollPosition;
|
||||
|
||||
// Load more when within PRELOAD_OFFSET pixels of bottom
|
||||
@@ -4711,17 +4715,17 @@ window.cancelAnimFrame = (function () {
|
||||
}
|
||||
|
||||
// Load previous when within PRELOAD_OFFSET pixels of top
|
||||
// Also ensure we aren't already at page 1
|
||||
if (window.scrollY < PRELOAD_OFFSET && infiniteState.hasPrev && !infiniteState.loadingUp) {
|
||||
loadPreviousItems();
|
||||
}
|
||||
|
||||
infiniteState.lastScrollY = window.scrollY;
|
||||
});
|
||||
};
|
||||
|
||||
// Store scroll handler reference for cleanup
|
||||
postsContainer._scrollHandler = onScroll;
|
||||
window.addEventListener("scroll", onScroll);
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
// Initial check (in case we loaded at top/bottom)
|
||||
setTimeout(onScroll, 100);
|
||||
@@ -8275,13 +8279,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const file = thumb.dataset.file;
|
||||
const mime = thumb.dataset.mime;
|
||||
|
||||
// Only preview videos/gifs
|
||||
if (!file || !mime || (!mime.startsWith('video/') && mime !== 'image/gif') || mime === 'video/youtube') return;
|
||||
// Only preview videos (not gifs — fetching a GIF causes main-thread freeze on click)
|
||||
if (!file || !mime || !mime.startsWith('video/') || mime === 'video/youtube') return;
|
||||
|
||||
// Helper to actually start video
|
||||
// Helper to actually start video — only called AFTER the hover delay
|
||||
// so we don't start a network fetch during the 150ms grace period.
|
||||
const run = () => {
|
||||
if (!document.body.contains(thumb)) return; // Thumb removed
|
||||
if (thumb !== activeThumb) return; // Switched away
|
||||
if (!document.body.contains(thumb)) return;
|
||||
if (thumb !== activeThumb) return;
|
||||
|
||||
activeThumb.classList.add('previewing');
|
||||
|
||||
@@ -8291,10 +8296,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
video.loop = true;
|
||||
video.className = 'preview-video';
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
// 'metadata' fetches only the file header (a few KB for duration/dimensions).
|
||||
// 'auto' fetches the entire file immediately — catastrophic for large GIFs.
|
||||
video.preload = 'metadata';
|
||||
|
||||
video.oncanplay = () => {
|
||||
video.play().catch(err => { /* Autoplay blocked */ });
|
||||
video.play().catch(() => {});
|
||||
video.classList.add('playing');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user