尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

react-use 传感器 Hook 实战:useMouse 与 useMouseHovered 鼠标位置追踪完全指南

react-use 传感器 Hook 实战:useMouse 与 useMouseHovered 鼠标位置追踪完全指南 react-use 传感器 Hook 实战useMouse 与 useMouseHovered 鼠标位置追踪完全指南【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use本文是 react-usev17.6.1中两个鼠标传感器 Hook 的完整技术指南useMouse负责在鼠标位置变化时驱动组件重渲染并返回坐标useMouseHovered在其基础上提供「悬停时才监听」与「坐标边界钳制」两种增强选项。读完本文你将掌握这两个 Hook 的全部返回值语义、完整用法、坐标换算原理文档坐标/元素坐标/视口坐标的关系以及底层基于requestAnimationFrame的更新机制可以直接在拖拽、跟随光标、悬浮提示等场景中落地使用。一、概览一对分工明确的鼠标传感器在 react-use 的文档体系中传感器Sensor类 Hook 的定义是监听某个界面状态的变化并用最新状态驱动组件重渲染见 docs/Sensors.md。useMouse与useMouseHovered正是其中的两个代表useMouse(ref)只做一件事——监听鼠标移动并返回鼠标位置、元素位置与元素尺寸useMouseHovered(ref, options)useMouse的增强版额外支持两个选项bound—— 将鼠标在元素内的相对坐标钳制在元素边界之内不再出现负值或超出宽高的值whenHovered—— 仅在鼠标悬停到该元素上时才挂载mousemove监听未悬停时完全不更新状态。两者都在src/index.ts中对外导出src/index.ts可以从react-use包名直接按具名方式引入。二、安装与引入react-use 以 npm 包形式分发CJS 产物lib/、ESM 产物esm/、类型声明lib/index.d.ts均已随包发布见 package.jsonnpm install react-use # 或 yarn add react-use在组件中按需引入import { useMouse, useMouseHovered } from react-use;仓库自带 Storybook 演示环境运行yarn storybook即start-storybook -p 6008后可在Sensors/useMouse分组中查看该 Hook 的文档页与实时 Demo对应源码 stories/useMouse.story.tsx。三、基础用法useMouse 完整示例useMouse接收一个指向 DOM 元素的ref返回包含 8 个字段的状态对象。原文档docs/useMouse.md给出的标准用法如下import {useMouse} from react-use; const Demo () { const ref React.useRef(null); const {docX, docY, posX, posY, elX, elY, elW, elH} useMouse(ref); return ( div ref{ref} divMouse position in document - x:{docX} y:{docY}/div divMouse position in element - x:{elX} y:{elY}/div divElement position- x:{posX} y:{posY}/div divElement dimensions - {elW}x{elH}/div /div ); };要点必须传入 ref 参数在开发模式下process.env.NODE_ENV developmentuseMouse会校验参数——若ref不是对象或ref.current为undefined会在控制台输出useMouse expects a single ref argument.错误提示见 src/useMouse.ts。因此务必在组件挂载后让ref.current指向真实存在的 DOM 节点8 个返回值全部为数值类型初始值均为 0类型定义见 src/useMouse.tsinterface State { docX: number; // 鼠标相对文档的位置含滚动 docY: number; posX: number; // 元素左上角相对文档的位置 posY: number; elX: number; // 鼠标相对元素左上角的位置 elY: number; elH: number; // 元素高度 elW: number; // 元素宽度 }返回值直接解构到 JSX 中使用即可坐标变化会驱动组件自动重渲染。四、状态字段语义与坐标换算原理理解 8 个字段前需要先厘清三种坐标系。useMouse在每次mousemove时执行的核心换算逻辑src/useMouse.ts如下const { left, top, width: elW, height: elH } ref.current.getBoundingClientRect(); const posX left window.pageXOffset; // 视口坐标 → 文档坐标 const posY top window.pageYOffset; const elX event.pageX - posX; // 文档坐标 → 元素相对坐标 const elY event.pageY - posY; setState({ docX: event.pageX, // 鼠标的文档坐标已包含滚动偏移 docY: event.pageY, posX, posY, // 元素左上角的文档坐标 elX, elY, elH, elW, // 元素内相对坐标与元素尺寸 });逐项说明字段含义计算方式docX/docY鼠标相对文档的位置直接取事件对象event.pageX/event.pageY已包含页面滚动偏移posX/posY元素左上角相对文档的位置getBoundingClientRect()返回的是视口坐标需加上window.pageXOffset/window.pageYOffset换算为文档坐标elX/elY鼠标相对元素左上角的位置event.pageX - posX/event.pageY - posY即鼠标进入元素内部的距离elW/elH元素的宽 / 高直接取自getBoundingClientRect()的width/height三条关键结论docX/docY受滚动影响elX/elY不受滚动影响因为posX/posY已经通过pageXOffset抵消了滚动所以元素内部的相对坐标天然与滚动无关适合用来定位元素内的光标标记elX/elY可以是负值或超出宽高鼠标位于元素左/上边界之外时相对坐标会小于 0位于右/下边界之外时会大于elW/elH。这正是useMouseHovered的bound选项要解决的问题elW/elH只在鼠标移动时刷新getBoundingClientRect()仅在mousemove处理器中调用若鼠标静止期间元素尺寸发生变化返回的宽高不会自动更新该结论由 src/useMouse.ts 的代码结构推断得出。需要持续追踪尺寸的场景应配合useMeasure等专用 Hook 使用。五、进阶用法useMouseHovered 与两个选项useMouseHovered的 API 签名与原文档 Reference 一致useMouseHovered(ref, {bound: false, whenHovered: false});两个选项均有默认值false类型定义见 src/useMouseHovered.tsinterface UseMouseHoveredOptions { whenHovered?: boolean; bound?: boolean; }5.1 whenHovered悬停时才更新当whenHovered: true时Hook 会在元素上挂载mouseover/mouseout监听由useHoverDirty实现见 src/useHoverDirty.ts只有鼠标真正悬停在元素内时useMouse才会持续更新状态鼠标离开后返回值冻结在离开瞬间的坐标。适合追求性能、不希望全局监听mousemove的场景。5.2 bound坐标边界钳制当bound: true时elX与elY会被钳制到元素边界内src/useMouseHovered.tsif (bound) { state.elX Math.max(0, Math.min(state.elX, state.elW)); state.elY Math.max(0, Math.min(state.elY, state.elH)); }即elX的取值区间被收敛为[0, elW]elY为[0, elH]不再出现负值或越界值。典型用途是绘制跟随光标但始终停留在元素内部的视觉元素例如把光标图标限制在画布内。5.3 组合示例画布内跟随光标下面是一个可直接运行的组合示例光标图标被限制在 400×400 的容器内且鼠标离开容器后坐标冻结import {useMouseHovered} from react-use; const CanvasDemo () { const ref React.useRef(null); const {elX, elY, elW, elH} useMouseHovered(ref, { bound: true, whenHovered: true, }); return ( div ref{ref} style{{ position: relative, width: 400, height: 400, border: 1px solid #333, overflow: hidden, }} span style{{ position: absolute, left: elX, top: elY, pointerEvents: none, transform: translate(-50%, -50%), }} /span div style{{position: absolute, bottom: 0}} elX: {elX.toFixed(0)} / elY: {elY.toFixed(0)} /div div style{{position: absolute, bottom: 16}} 元素尺寸: {elW} x {elH} /div /div ); };bound: true保证图标不会溢出容器配合overflow: hidden双保险whenHovered: true保证鼠标不在容器内时不会产生任何mousemove更新。仓库自带的 Storybook Demostories/useMouse.story.tsx采用了类似思路用一个position: relative的 400×400 灰底方块承载一个以elX/elY定位、pointerEvents: none的悬浮图标并通过JSON.stringify实时打印全部状态字段可作为调试参考。六、源码深度解析useMouse 的实现机制6.1 全局监听与清理useMouse通过useEffect在document上注册mousemove监听src/useMouse.tson(document, mousemove, moveHandler); return () { off(document, mousemove, moveHandler); };事件挂载在document而非元素上因此只要鼠标在页面内移动不限于元素内就会触发更新elX/elY也可能随之出现负值/越界值——这正是bound选项存在的意义on/off是 src/misc/util.ts 提供的工具函数内部做了能力检测obj.addEventListener存在才绑定并兼容addEventListener的完整参数签名useEffect的依赖数组为[ref]当ref变化时旧监听会被移除、新监听被挂载组件卸载时同样会清理不会产生内存泄漏由于监听逻辑全部位于useEffect内服务端渲染SSR期间不会执行任何 DOM 操作该 Hook 在 SSR 环境下是安全的。6.2 requestAnimationFrame 节流useRafStatemousemove事件的触发频率远高于 React 的重渲染能力若每次事件都直接setState会造成大量浪费。useMouse因此引入了 src/useRafState.ts 作为状态容器其核心是用requestAnimationFrame对齐渲染帧src/useRafState.tsconst setRafState useCallback((value) { cancelAnimationFrame(frame.current); frame.current requestAnimationFrame(() { setState(value); }); }, []);要点一帧只更新一次同一帧内多次mousemove只会触发一次 React 状态更新与浏览器的绘制节奏对齐帧内合并每次调用前先cancelAnimationFrame取消上一帧的挂起任务保证只提交该帧内最后一次坐标避免中间值闪烁卸载清理通过useUnmount在组件卸载时取消尚未执行的动画帧回调杜绝卸载后 setState 的告警。换句话说从useMouse读到的坐标始终是最新一次mousemove的值但重渲染频率被限制在帧率以内这是该 Hook 高性能的关键所在。6.3 开发模式校验if (process.env.NODE_ENV development) { if (typeof ref ! object || typeof ref.current undefined) { console.error(useMouse expects a single ref argument.); } }在开发模式下若传入的不是合法 ref例如直接传入null、忘记useRef或 ref 尚未挂载控制台会给出明确提示src/useMouse.ts。注意该检查只做提示、不阻断运行ref.current为空时moveHandler会直接提前返回src/useMouse.ts状态保持初始值 0。七、useMouseHovered 内部实现剖析useMouseHovered的完整实现只有 25 行src/useMouseHovered.ts却包含了两个值得学习的技巧7.1 空 ref 技巧优雅地暂停更新const nullRef { current: null }; const isHovered useHoverDirty(ref, whenHovered); const state useMouse(whenHovered !isHovered ? nullRef : ref);当whenHovered: true且当前未悬停时useMouse收到的是一个{ current: null }的空 ref。由于useMouse的moveHandler在ref.current为空时会提前返回src/useMouse.ts状态便自然冻结无需额外开关变量。这是一个典型的「以数据流控制行为」的声明式写法。悬停检测本身由useHoverDirty完成src/useHoverDirty.ts在ref.current元素上直接挂载原生mouseover/mouseout监听而非 React 合成事件进入置true、离开置false它同样在useEffect中注册与清理依赖数组为[enabled, ref]并预先保存const { current } ref以避免闭包引用过期元素注释中也说明了这是为了规避react-hooks/exhaustive-deps告警。7.2 bound 的钳制算法if (bound) { state.elX Math.max(0, Math.min(state.elX, state.elW)); state.elY Math.max(0, Math.min(state.elY, state.elH)); }用Math.min(state.elX, state.elW)封顶、Math.max(0, ...)保底一行公式同时处理负值与越界两种情况。注意这里是原地修改返回的state对象不产生新的对象引用whenHovered与bound相互独立可按需任意组合。八、使用注意事项与边界情况综合文档与源码实现使用时需要注意以下几点必须持有已挂载的 DOM refref.current为空时状态永远停在初始值 0。若 ref 指向的节点在渲染后才出现如条件渲染可结合useEffect在挂载后再调用 Hook或保证容器节点始终渲染elW/elH不是独立的尺寸传感器它们只在mousemove触发时被计算鼠标静止期间的元素尺寸变化不会反映到返回值中如需实时尺寸请改用useMeasure等专用 Hook返回对象引用随移动而变每次有效mousemove都会经useRafState产生一个新对象一帧最多一次若把它直接作为useEffect依赖或传入 memo 子组件会按帧率触发重新计算/重渲染全局监听的成本useMouse在document上监听mousemove即使鼠标不经过目标元素也会更新状态仅关心元素内光标时优先使用useMouseHovered(ref, { whenHovered: true })来降低开销bound只影响elX/elYdocX/docY、posX/posY不会被钳制需要边界内的文档坐标时请自行基于elX/elY反推。九、仓库中的参考资源如需进一步研究或验证可在当前仓库中查阅以下文件原始文档docs/useMouse.md包含官方用法示例与 API Reference核心实现src/useMouse.ts事件监听、坐标换算、开发模式校验、src/useMouseHovered.tsbound/whenHovered选项底层依赖src/useRafState.tsrequestAnimationFrame节流状态、src/useHoverDirty.ts原生悬停检测、src/misc/util.tson/off事件工具对外导出src/index.ts可运行 Demostories/useMouse.story.tsx通过yarn storybook在 6008 端口启动后即可交互体验传感器 Hook 的整体定位说明docs/Sensors.md。十、小结useMouse与useMouseHovered是 react-use 中一对实现简洁但用途广泛的鼠标传感器 Hook前者以文档级mousemove监听 getBoundingClientRect坐标换算 requestAnimationFrame节流稳定提供文档坐标、元素坐标与元素尺寸三组信息后者通过空 ref 切换与边界钳制两个小技巧优雅地补上了「悬停感知」与「边界约束」两大高频需求。理解其坐标体系与更新机制后无论是实现拖拽跟随、画布光标、还是悬浮提示定位都能准确预测它的行为边界。【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表