fix: 用 IntersectionObserver 替代 scroll 事件实现瀑布流

scroll 事件在某些布局下不触发,改用 IntersectionObserver
监听列表底部哨兵元素,进入视口时自动加载下一页,更可靠。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-01 23:59:54 +08:00
parent b7b254546c
commit 54c8449f7b

View File

@@ -115,8 +115,6 @@ export default function MonitoringView() {
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [showBackToTop, setShowBackToTop] = useState(false); const [showBackToTop, setShowBackToTop] = useState(false);
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
const listEndRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const departments = filterOptions.departments; const departments = filterOptions.departments;
const plateNumbers = filterOptions.plates; const plateNumbers = filterOptions.plates;
@@ -178,26 +176,34 @@ export default function MonitoringView() {
loadFirstPage(); loadFirstPage();
}, [loadFirstPage]); }, [loadFirstPage]);
// 滚动触底检测 + 回到顶部按钮 // 触底检测:用 IntersectionObserver 监听哨兵元素
const loadMoreRef = useRef(loadMore); const loadMoreRef = useRef(loadMore);
loadMoreRef.current = loadMore; loadMoreRef.current = loadMore;
const sentinelRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMoreRef.current();
}
},
{ rootMargin: '200px' }
);
observer.observe(sentinel);
return () => observer.disconnect();
}, []);
// 回到顶部按钮
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const scrollY = window.scrollY || document.documentElement.scrollTop; const scrollY = window.scrollY || document.documentElement.scrollTop;
setShowBackToTop(scrollY > 400); setShowBackToTop(scrollY > 400);
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = window.innerHeight;
if (scrollHeight - scrollY - clientHeight < 300) {
loadMoreRef.current();
}
}; };
window.addEventListener('scroll', handleScroll, { passive: true }); window.addEventListener('scroll', handleScroll, { passive: true });
document.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
document.removeEventListener('scroll', handleScroll);
};
}, []); }, []);
const scrollToTop = () => { const scrollToTop = () => {
@@ -802,7 +808,8 @@ export default function MonitoringView() {
<span className="text-[10px] font-bold text-slate-300"> {total} </span> <span className="text-[10px] font-bold text-slate-300"> {total} </span>
</div> </div>
)} )}
<div ref={listEndRef} /> {/* 哨兵元素:进入视口时触发加载更多 */}
<div ref={sentinelRef} className="h-1" />
</div> </div>
{/* 回到顶部按钮 */} {/* 回到顶部按钮 */}