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

如何在React(Next.js+Prismic)项目中不使用useRef实现scrollspy导航

解决方案

核心逻辑是无需将切片ref提到页面层级,通过DOM唯一标识结合Intersection ObserverAPI实现滚动监听,性能远高于原生onscroll事件,改造成本极低。


具体实现步骤

  • 第一步:收集导航映射关系
    Prismic返回的每个切片自带唯一slice.id,可直接作为锚点标识,也可在切片模型中新增自定义锚点字段适配业务需求。遍历切片数组时过滤掉导航切片,收集所有内容切片的ID、导航显示文本,直接传给导航组件即可。
  • 第二步:关联内容切片与标识
    两种可选方式,无需改造切片层级向上传递ref:
    • 手动给每个内容切片的根DOM元素添加属性id={slice.id},每个切片仅需新增一行代码
    • 无需修改切片代码,直接使用SliceZone默认给每个切片根元素挂载的data-slice-id属性做匹配
  • 第三步:实现视口监听逻辑
    以下是页面层级的完整示例代码:
// Next.js页面组件
import { SliceZone } from "@prismicio/react";
import { useEffect, useState, useMemo } from "react";
import StickyNav from "./StickyNav";

export default function ContentPage({ slices }) {
  // 收集导航项配置
  const navItems = useMemo(() => {
    return slices
      .filter(slice => slice.slice_type !== "navigation_slice") // 替换为你的导航切片类型
      .map(slice => ({
        id: slice.id,
        label: slice.primary.nav_label // 替换为切片中存储导航文本的字段
      }))
  }, [slices])

  const [activeId, setActiveId] = useState(navItems[0]?.id || "")

  useEffect(() => {
    // 调整rootMargin适配吸顶导航高度,控制高亮触发的视口范围
    // 示例参数表示:视口顶部减去64px(导航高度)、底部减去60%的区域为激活判定区
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setActiveId(entry.target.id || entry.dataset.sliceId)
        }
      })
    }, { rootMargin: "-64px 0px -60% 0px" })

    // 绑定所有内容切片DOM
    navItems.forEach(item => {
      // 如果用data-slice-id匹配,替换为document.querySelector(`[data-slice-id="${item.id}"]`)
      const el = document.getElementById(item.id)
      if (el) observer.observe(el)
    })

    // 组件卸载时清理监听
    return () => {
      navItems.forEach(item => {
        const el = document.getElementById(item.id)
        if (el) observer.unobserve(el)
      })
    }
  }, [navItems])

  return (
    <main>
      {/* 顶部前置内容,让导航初始处于半腰位置 */}
      <div className="h-[50vh] bg-slate-100">
        {/* 你的头部banner内容 */}
      </div>
      {/* 已实现吸顶效果的导航组件,传入激活ID即可 */}
      <StickyNav items={navItems} activeId={activeId} />
      {/* 切片渲染区域 */}
      <SliceZone slices={slices} components={yourSliceComponentMap} />
    </main>
  )
}

导航组件高亮逻辑

直接匹配传入的activeId添加高亮样式即可,示例代码如下:

// StickyNav.jsx
export default function StickyNav({ items, activeId }) {
  return (
    <nav className="sticky top-0 w-full h-16 bg-white shadow z-50 flex items-center justify-around">
      {items.map(item => (
        <a
          key={item.id}
          href={`#${item.id}`}
          className={`transition-colors ${activeId === item.id ? "text-blue-600 font-bold" : "text-gray-600"}`}
        >
          {item.label}
        </a>
      ))}
    </nav>
  )
}

如果需要平滑滚动,可给a标签添加scroll-behavior: smooth全局样式,或者绑定点击事件调用window.scrollTo实现自定义滚动动画。

内容的提问来源于stack exchange,提问作者David Fox

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 13:54:06