diff options
| author | Claude <noreply@anthropic.com> | 2026-02-16 22:58:54 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-02-16 22:58:54 +0000 |
| commit | 4555ee294a5acdfbc4b1026d99df54d07c74ed97 (patch) | |
| tree | 32cb971915aaa560e6fa433a5b8aee3cfbd5f8cf /frontend-vanilla/src/main.ts | |
| parent | 2321d5bcf17e416244e75f5727f0710f9cdd9a1a (diff) | |
| download | neko-4555ee294a5acdfbc4b1026d99df54d07c74ed97.tar.gz neko-4555ee294a5acdfbc4b1026d99df54d07c74ed97.tar.bz2 neko-4555ee294a5acdfbc4b1026d99df54d07c74ed97.zip | |
Fix mobile scroll not marking items as read in v3 UI
Two issues prevented IntersectionObserver from firing on mobile:
1. threshold: 1.0 required items to be 100% visible, but on mobile
viewports items with descriptions are often taller than the screen
2. root: null used the viewport, but scrolling happens inside
.main-content (overflow-y: auto) while body has overflow: hidden
Switched to "scrolled past" pattern: items are marked read when they
scroll above the viewport (like the working legacy UI). Set root to
the actual scroll container element.
https://claude.ai/code/session_01NSUnBzNrgQVUNg9PnugF7N
Diffstat (limited to 'frontend-vanilla/src/main.ts')
| -rw-r--r-- | frontend-vanilla/src/main.ts | 11 |
1 files changed, 7 insertions, 4 deletions
diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts index 94f5727..c310144 100644 --- a/frontend-vanilla/src/main.ts +++ b/frontend-vanilla/src/main.ts @@ -262,6 +262,9 @@ export function renderItems() { ${store.hasMore ? '<div id="load-more-sentinel" class="loading-more">Loading more...</div>' : ''} `; + // Use the actual scroll container as IntersectionObserver root + const scrollRoot = document.getElementById('main-content'); + // Setup infinite scroll const sentinel = document.getElementById('load-more-sentinel'); if (sentinel) { @@ -269,14 +272,14 @@ export function renderItems() { if (entries[0].isIntersecting && !store.loading && store.hasMore) { loadMore(); } - }, { threshold: 0.1 }); + }, { root: scrollRoot, threshold: 0.1 }); observer.observe(sentinel); } - // Setup item observer for marking read + // Setup item observer for marking read when items scroll past (above viewport) itemObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { - if (entry.isIntersecting) { + if (!entry.isIntersecting && entry.boundingClientRect.bottom < (entry.rootBounds?.top ?? 0)) { const target = entry.target as HTMLElement; const id = parseInt(target.getAttribute('data-id') || '0'); if (id) { @@ -288,7 +291,7 @@ export function renderItems() { } } }); - }, { threshold: 1.0 }); + }, { root: scrollRoot, threshold: 0 }); contentArea.querySelectorAll('.feed-item').forEach(el => itemObserver!.observe(el)); } |
