如何实现可获取元素相对于窗口顶部偏移量的可复用React Hook?
实现可监听元素偏移量的React Hook方案
你的代码核心问题在于:React 的 ref 对象是稳定引用,ref.current 的 mutable 变更不会触发组件重渲染,也不会触发依赖 ref.current 的 useEffect 执行,因此只能拿到初始挂载时的偏移值。另外原生 DOM 元素不存在 offsetBottom、offsetRight 属性,需要基于已有属性计算得到。
以下是可直接复用的实现方案:
import { useState, useEffect, useRef, type RefObject } from 'react'; interface ElementPosition { left: number | undefined; right: number | undefined; top: number | undefined; bottom: number | undefined; } export const useElementPosition = (elementRef: RefObject<HTMLElement>): ElementPosition => { const [position, setPosition] = useState<ElementPosition>({ left: undefined, right: undefined, top: undefined, bottom: undefined, }); // 存储 observer 引用方便清理 const resizeObserverRef = useRef<ResizeObserver | null>(null); useEffect(() => { const element = elementRef.current; if (!element) return; // 位置更新函数 const updatePosition = () => { const offsetTop = element.offsetTop; const offsetLeft = element.offsetLeft; const offsetHeight = element.offsetHeight; const offsetWidth = element.offsetWidth; setPosition({ top: offsetTop, bottom: offsetTop + offsetHeight, left: offsetLeft, right: offsetLeft + offsetWidth, }); }; // 初始化更新一次 updatePosition(); // 监听元素尺寸变化 resizeObserverRef.current = new ResizeObserver(updatePosition); resizeObserverRef.current.observe(element); // 监听页面滚动、窗口 resize 事件,覆盖滚动导致的相对偏移变化、窗口尺寸变化场景 window.addEventListener('scroll', updatePosition, true); // 捕获阶段监听,覆盖内部滚动容器场景 window.addEventListener('resize', updatePosition); // 清理函数 return () => { resizeObserverRef.current?.disconnect(); window.removeEventListener('scroll', updatePosition, true); window.removeEventListener('resize', updatePosition); }; }, [elementRef]); // 依赖稳定的ref对象本身,挂载时执行一次监听逻辑即可 return position; };
使用方式
- 给目标元素绑定 ref 后直接调用即可,适配表头吸顶场景:
const TableHeader = () => { const headerRef = useRef<HTMLTableSectionElement>(null); const headerPosition = useElementPosition(headerRef); const isSticky = window.scrollY >= (headerPosition.top ?? 0); return ( <thead ref={headerRef} className={isSticky ? 'sticky top-0 shadow' : ''}> {/* 表头内容 */} </thead> ) }
补充优化点
- 如果你的滚动容器不是 window 而是自定义的滚动元素,可以把滚动容器的 ref 也作为参数传入 Hook,替换掉 window 的事件监听,减少不必要的事件触发
- 如果仅需要判断元素是否达到吸顶阈值,优先用
IntersectionObserver实现,性能远高于全局监听 scroll 事件
内容的提问来源于stack exchange,提问作者howard wolowitz
相关产品推荐
相关产品推荐

