如何在Next.js中实现跟随内容滚动、触底固定的粘性侧边栏
实现方案
方案1:纯CSS实现(优先推荐,性能最优)
利用CSS原生position: sticky即可实现需求,无需JS逻辑,适配Next.js所有版本,无额外性能开销。
样式代码
/* 页面外层容器 */ .page-container { display: flex; gap: 24px; align-items: flex-start; max-width: 1200px; margin: 0 auto; padding: 20px; } /* Feed流主内容区 */ .main-content { flex: 1; } /* 侧边栏 */ .sidebar { width: 300px; position: sticky; top: 20px; /* 可自定义滚动时侧边栏距离视口顶部的间距 */ max-height: calc(100vh - 40px); /* 减去上下边距,避免侧边栏超出视口 */ overflow-y: auto; /* 侧边栏内容超出时自身可滚动 */ }
组件代码
// App Router 对应 app/page.tsx,Pages Router 对应 pages/index.tsx import './page.css' export default function Home() { return ( <div className="page-container"> <div className="main-content"> {/* 替换为你的Feed流内容 */} <h1>Feed 内容</h1> {Array.from({length: 100}).map((_, i) => ( <p key={i}>Feed 内容项 {i+1}</p> ))} </div> <div className="sidebar"> {/* 替换为你的侧边栏内容 */} <h2>侧边栏</h2> {Array.from({length: 50}).map((_, i) => ( <p key={i}>侧边栏内容项 {i+1}</p> ))} </div> </div> ) }
注意:使用该方案需确保侧边栏的所有父级元素都没有设置
overflow: hidden、overflow: auto等会影响sticky生效的属性。
方案2:JS实现(适用于需要自定义交互逻辑的场景)
如果有特殊交互需求(如滚动到特定位置才触发固定、动态调整侧边栏高度等),可以通过自定义React Hook监听滚动事件实现:
自定义Hook代码
// hooks/useStickySidebar.ts import { useEffect, useRef, useState } from 'react' export const useStickySidebar = (topOffset = 20) => { const sidebarRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null) const [translateY, setTranslateY] = useState(0) useEffect(() => { const handleScroll = () => { if (!sidebarRef.current || !containerRef.current) return const containerRect = containerRef.current.getBoundingClientRect() const sidebarRect = sidebarRef.current.getBoundingClientRect() const viewportHeight = window.innerHeight const sidebarHeight = sidebarRect.height const containerTop = containerRef.current.offsetTop const containerBottom = containerTop + containerRect.height const scrollTop = window.scrollY // 侧边栏高度小于视口时直接固定在顶部 if (sidebarHeight < viewportHeight) { setTranslateY(Math.max(0, topOffset - containerRect.top)) return } // 侧边栏高度大于视口时的滚动逻辑 const maxScroll = sidebarHeight - viewportHeight + topOffset const currentScroll = scrollTop - containerTop + topOffset if (currentScroll <= 0) { setTranslateY(0) } else if (currentScroll >= maxScroll) { setTranslateY(maxScroll) } else { setTranslateY(currentScroll) } // 页面容器滚动到底部时,防止侧边栏超出容器范围 if (scrollTop + viewportHeight >= containerBottom) { setTranslateY(containerBottom - containerTop - sidebarHeight) } } window.addEventListener('scroll', handleScroll, { passive: true }) handleScroll() // 初始化计算位置 return () => window.removeEventListener('scroll', handleScroll) }, [topOffset]) return { sidebarRef, containerRef, translateY } }
组件使用代码
// App Router 对应 app/page.tsx,Pages Router 无需加 'use client' 'use client' import { useStickySidebar } from '@/hooks/useStickySidebar' export default function Home() { const { sidebarRef, containerRef, translateY } = useStickySidebar(20) return ( <div ref={containerRef} className="flex gap-6 max-w-7xl mx-auto p-5"> <div className="flex-1 min-h-[2000px]"> {/* 替换为你的Feed流内容 */} {Array.from({length: 100}).map((_, i) => <p key={i}>Feed 内容项 {i+1}</p>)} </div> <div ref={sidebarRef} className="w-[300px] max-h-[calc(100vh-40px)] overflow-y-auto" style={{ transform: `translateY(${translateY}px)` }} > {/* 替换为你的侧边栏内容 */} {Array.from({length: 50}).map((_, i) => <p key={i}>侧边栏内容项 {i+1}</p>)} </div> </div> ) }
内容的提问来源于stack exchange,提问作者Aadhit
相关产品推荐
相关产品推荐

