如何在React(Next.js+Prismic)项目中不使用useRef实现scrollspy导航
解决方案
核心逻辑是无需将切片ref提到页面层级,通过DOM唯一标识结合Intersection ObserverAPI实现滚动监听,性能远高于原生onscroll事件,改造成本极低。
具体实现步骤
- 第一步:收集导航映射关系
Prismic返回的每个切片自带唯一slice.id,可直接作为锚点标识,也可在切片模型中新增自定义锚点字段适配业务需求。遍历切片数组时过滤掉导航切片,收集所有内容切片的ID、导航显示文本,直接传给导航组件即可。 - 第二步:关联内容切片与标识
两种可选方式,无需改造切片层级向上传递ref:- 手动给每个内容切片的根DOM元素添加属性
id={slice.id},每个切片仅需新增一行代码 - 无需修改切片代码,直接使用SliceZone默认给每个切片根元素挂载的
data-slice-id属性做匹配
- 手动给每个内容切片的根DOM元素添加属性
- 第三步:实现视口监听逻辑
以下是页面层级的完整示例代码:
// 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
相关产品推荐
相关产品推荐

