You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 21:15:02