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

如何实现可获取元素相对于窗口顶部偏移量的可复用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 08:18:04