- 前端
- 开发工具
【免费下载链接】beautiful-react-hooks
🔥 A collection of beautiful and (hopefully) useful React hooks to speed-up your components and hooks development 🔥
导读
useSwipe是 beautiful-react-hooks 提供的一个用于追踪滑动手势(swipe gesture)状态的 React Hook,它统一封装了鼠标事件与触摸事件,让开发者无论面对移动端还是桌面端用户,都能以同一套 API 读取当前滑动的方向、位移和次数。本文将以官方文档 docs/useSwipe.md 为主线,结合仓库内 src/useSwipe.ts、src/shared/swipeUtils.ts 等源码实现与 test/useSwipe.spec.js 测试用例,完整讲解它的使用方式、可配置项、返回值语义与底层工作原理,读完后你将能直接在自己的组件中实现可复用的滑动交互(如轮播、抽屉、手势引导等)。
为什么需要 useSwipe
在 Web 应用中,"滑动"是移动端最常见的交互,但桌面端用户也会通过鼠标拖拽产生类似行为。手动处理这两套事件不仅代码冗余,还容易遗漏边界情况。useSwipe主要解决以下痛点:
- 快速获取最近一次滑动数据:无需自己维护坐标起点、位移增量等状态;
- 同时注册鼠标与触摸事件监听:根据传入的 DOM ref 决定是绑定到目标元素还是全局 document;
- 组件卸载时自动移除监听:由底层
useEvent的useEffect清理逻辑保证,避免内存泄漏; - 将滑动逻辑抽象为可复用 Hook:业务组件只需消费返回的
SwipeState,无需关心事件细节。
这些动机在 docs/useSwipe.md 的 "Why?" 一节中有明确说明,也是该 Hook 设计的目标。
安装与引入
beautiful-react-hooks 是一个按需导出的 ESM/CJS 双格式库(见 package.json 中的exports字段),你可以通过子路径直接引入单个 Hook,避免打包进无关代码:
npm install beautiful-react-hooks # 或 yarn add beautiful-react-hooks代码中按需引入:
import useSwipe from 'beautiful-react-hooks/useSwipe';当前仓库的 peerDependencies 要求react >= 18.2.0 < 20.0.0,使用前请确认你的 React 版本满足要求(见 package.json)。
基础用法:绑定到指定 DOM 元素
useSwipe的第一个参数是一个 DOM ref。传入 ref 后,鼠标与触摸事件将只会绑定在该元素上:
import { useRef, useState } from 'react'; import useSwipe from 'beautiful-react-hooks/useSwipe'; const SwipeReporter = () => { const ref = useRef(); const swipeState = useSwipe(ref); const showDetail = swipeState.count > 0 || swipeState.swiping; return ( <DisplayDemo title="useSwipe"> <div ref={ref} style={{ padding: 20, background: '#A1B5D8' }}> Swipe me! {showDetail && ( <div> <p>Swipe information:</p> <p>Is swiping: {swipeState.swiping ? 'yes' : 'no'}</p> <p>Direction: {swipeState.direction}</p> <p>Alpha-x: {swipeState.alphaX}, Alpha-y: {swipeState.alphaY} </p> <p>Swipe count: {swipeState.count}</p> </div> )} </div> </DisplayDemo> ); }; <SwipeReporter />要点:
- ref 必须指向一个真实存在的 DOM 节点(
HTMLElement),Hook 内部为泛型<TElement extends HTMLElement>; - 返回的
swipeState是受控状态对象,随滑动过程实时更新,因此组件会在滑动时自动重渲染; showDetail用于在真正发生滑动后才展示细节面板,避免初始空状态刷屏。
全局事件模式:不传 ref
如果不传任何参数,useSwipe会把监听器绑定到全局window.document上,此时页面任意位置的滑动都会被捕获:
import { useRef, useState } from 'react'; import useSwipe from 'beautiful-react-hooks/useSwipe'; const SwipeReporter = () => { const swipeState = useSwipe(); const showDetail = swipeState.count > 0 || swipeState.swiping; return ( <DisplayDemo title="useSwipe"> <div style={{ padding: 20, background: '#A1B5D8' }}> Swipe everywehere you want! {showDetail && ( <div> <p>Swipe information:</p> <p>Is swiping: {swipeState.swiping ? 'yes' : 'no'}</p> <p>Direction: {swipeState.direction}</p> <p>Alpha-x: {swipeState.alphaX}, Alpha-y: {swipeState.alphaY} </p> <p>Swipe count: {swipeState.count}</p> </div> )} </div> </DisplayDemo> ); }; <SwipeReporter />从源码看,这一行为由底层事件 Hook 保证:在 useMouseEvents.ts 与 useTouchEvents.ts 中,当targetRef未提供时,会回退为{ current: window.document },从而将事件全局绑定到 document。
全局模式的典型场景:整页手势导航(如翻页、返回)、全屏滑动手势统计等。
Options 配置项详解
useSwipe的第二个参数是可选配置对象,官方文档给出了三个核心选项,而源码类型定义中还有一个额外的passive字段:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
direction | 'both' \| 'horizontal' \| 'vertical' | 'both' | 允许滑动的方向;horizontal/vertical会过滤另一方向的位移 |
threshold | number | 见下方说明 | 触发"滑动中"状态所需的最小位移像素数 |
preventDefault | boolean | true | 是否在滑动过程中调用event.preventDefault()与event.stopPropagation() |
passive | boolean | undefined | 透传给addEventListener的passive选项,用于优化滚动性能 |
关于threshold默认值的说明:官方文档标注为15,而当前仓库源码 src/useSwipe.ts 中useSwipe自身的默认值是10;与此同时,useHorizontalSwipe与useVerticalSwipe两个快捷变体(见 src/useHorizontalSwipe.ts、src/useVerticalSwipe.ts)的默认阈值则是15。由于文档与源码存在版本差异,建议在实际项目里显式指定threshold,不依赖默认值,以保证行为符合预期。
带配置的完整示例:
import { useRef, useState } from 'react'; import useSwipe from 'beautiful-react-hooks/useSwipe'; const SwipeReporter = () => { const ref = useRef(); const options = { direction: 'horizontal', threshold: 10, preventDefault: true }; const swipeState = useSwipe(ref, options); const showDetail = swipeState.count > 0 || swipeState.swiping; return ( <DisplayDemo title="useSwipe"> <div ref={ref} style={{ padding: 20, background: '#A1B5D8' }}> Swipe me, horizontally... {showDetail && ( <div> <p>Swipe information:</p> <p>Is swiping: {swipeState.swiping ? 'yes' : 'no'}</p> <p>Direction: {swipeState.direction}</p> <p>Alpha-x: {swipeState.alphaX}, Alpha-y: {swipeState.alphaY} </p> <p>Swipe count: {swipeState.count}</p> </div> )} </div> </DisplayDemo> ); }; <SwipeReporter />各选项的源码级行为
direction:在 src/useSwipe.ts 的continueSwipe中按三种模式分别处理——'both'模式下横纵位移任一超过阈值即触发滑动,'horizontal'只关注alpha[0],'vertical'只关注alpha[1],且单向模式下另一轴位移会被强制置 0;threshold:与Math.abs(alpha)比较,用于过滤手指/鼠标抖动产生的微小位移,避免误触发;preventDefault:为true时,在startSwipe、continueSwipe、endSwipe三处都会调用event.preventDefault()和event.stopPropagation()(见 src/useSwipe.ts),可阻止页面滚动等默认行为,但也意味着会打断页面滚动,需按场景取舍;passive:传入 useMouseEvents.ts 与 useTouchEvents.ts 后透传给useEvent,最终作为AddEventListenerOptions传给addEventListener。注意:当passive: true时浏览器会忽略preventDefault()调用,两者不要同时依赖。
返回值 SwipeState 语义
Hook 每次渲染返回一个SwipeState对象,初始值为{ swiping: false, direction: undefined, alphaX: 0, alphaY: 0, count: 0 }(见 src/useSwipe.ts):
| 字段 | 类型 | 含义 |
|---|---|---|
swiping | boolean | 当前是否正处于滑动中(位移已超过阈值) |
direction | 'right' \| 'left' \| 'down' \| 'up' | 最近一次滑动的方向;未滑动时为undefined |
alphaX | number | 起始点与当前点的横向位移差(起始点 x − 当前点 x) |
alphaY | number | 起始点与当前点的纵向位移差(起始点 y − 当前点 y) |
count | number | 已经完成的滑动次数 |
值得注意的语义细节:
alphaX/alphaY是带符号的位移量,负值代表手指向右/向下移动,方向判定正是基于其符号(见下文源码分析);count只在一次滑动"结束"时递增(mouseup/touchend/mouseleave/touchcancel),滑动中途不会变化,因此常用来判断"是否完成过至少一次滑动"。
源码级原理:手势生命周期与方向判定
三阶段事件流程
从 src/useSwipe.ts 可以看出,Hook 内部把一次滑动拆成三个阶段,分别映射到鼠标/触摸事件对:
| 阶段 | 处理函数 | 绑定事件 | 核心职责 |
|---|---|---|---|
| 开始 | startSwipe | mousedown/touchstart | 记录起始点坐标到startingPointRef |
| 进行 | continueSwipe | mousemove/touchmove | 计算位移差,超过阈值后更新swiping、alphaX/alphaY、direction |
| 结束 | endSwipe | mouseup/touchend,以及mouseleave/touchcancel | 若正在滑动则递增count并复位swiping,重置起始点 |
其中mouseleave与touchcancel的绑定是为了处理"手指/鼠标滑出元素或系统中断触摸"的边界情况,保证状态始终能复位,不会卡在swiping: true。
位移与方向的计算
坐标提取与方向判定集中在 src/shared/swipeUtils.ts:
getPointerCoordinates:优先读取event.touches[0]的clientX/clientY(触摸事件),否则回退到MouseEvent的clientX/clientY,从而统一两种事件源;getHorizontalDirection(alpha):alpha < 0返回'right',否则返回'left';getVerticalDirection(alpha):alpha < 0返回'down',否则返回'up';getDirection(currentPoint, startingPoint, alpha):比较横纵位移的绝对值,位移较大的轴决定最终方向(斜向滑动时给出主方向)。
方向含义速查:由于alpha = 起始点 − 当前点,手指向右滑动时alphaX < 0,因此返回'right';手指向下滑动时alphaY < 0,返回'down',语义直观。
状态更新的防抖优化
源码在每次更新前通过isEqual比较新旧状态(src/useSwipe.ts),只有swiping、direction、count、alphaX、alphaY任一发生变化才调用setState,避免高频mousemove/touchmove触发无意义重渲染。
事件绑定与自动清理机制
useSwipe并不直接调用addEventListener,而是组合了两个更底层的事件 Hook:
- useMouseEvents.ts 提供
onMouseDown/onMouseMove/onMouseUp/onMouseLeave等回调注册器; - useTouchEvents.ts 提供
onTouchStart/onTouchMove/onTouchEnd/onTouchCancel回调注册器;
两者最终都经由 useEvent.ts 的useEffect完成监听注册与清理:useEffect的依赖包含eventName、target.current与options,并在 cleanup 中调用removeEventListener。因此当组件卸载、ref 指向的 DOM 被替换或事件配置变化时,旧监听会被自动移除,这正是文档中"自动移除监听"承诺的实现基础。
快捷变体:useHorizontalSwipe 与 useVerticalSwipe
仓库为最常见的单向滑动提供了两个语法糖:
- useHorizontalSwipe.ts:内部强制
direction: 'horizontal',默认阈值 15; - useVerticalSwipe.ts:内部强制
direction: 'vertical',默认阈值 15。
它们与useSwipe返回完全相同的SwipeState结构,适合明确只需要横向或纵向滑动判断的场景,写法更简洁、语义更清晰:
import useHorizontalSwipe from 'beautiful-react-hooks/useHorizontalSwipe'; import useVerticalSwipe from 'beautiful-react-hooks/useVerticalSwipe';测试验证
仓库在 test/useSwipe.spec.js 中为三个相关 Hook 编写了单元测试,关键断言包括:
useSwipe是命名以use开头的 Hook 函数(通过assertHook工具校验);- 调用
useSwipe()后返回值是一个包含swiping、direction、alphaX、alphaY、count五个键的对象; useHorizontalSwipe与useVerticalSwipe同样返回上述五键结构。
你可以通过以下命令在本地复跑测试确认行为(见 package.json 的scripts):
npm install npm test使用建议与注意事项
- 明确指定
threshold:文档默认值(15)与源码默认值(10)存在差异,生产中建议显式传入,避免版本差异导致的手感不一致; - 按需选择绑定方式:传入 ref 将手势限制在局部元素,适合卡片、轮播;不传 ref 则为全局监听,适合整页手势,但注意避免与页内其他拖拽交互冲突;
preventDefault与滚动:preventDefault: true会阻止页面滚动与事件冒泡,若你的目标元素本身需要可滚动,应谨慎开启,或搭配passive选项按需组合;- 状态驱动渲染:
swipeState是 React 状态,高频移动时会触发重渲染,配合isEqual优化虽已减少无效更新,但若追求极致性能,可在回调中消费数据而非直接渲染全量状态; - TypeScript 友好:
useSwipe<TElement extends HTMLElement>为泛型设计,UseSwipeOptions与SwipeState接口均已导出(见 src/useSwipe.ts),可在类型安全的前提下传入RefObject<HTMLElement>。
延伸阅读
- 官方文档:docs/useSwipe.md
- 核心实现:src/useSwipe.ts
- 方向与坐标工具:src/shared/swipeUtils.ts
- 底层事件 Hook:src/useEvent.ts、src/useMouseEvents.ts、src/useTouchEvents.ts
- 测试用例:test/useSwipe.spec.js
若你的交互需要更细粒度的事件级控制(而非聚合状态),可以进一步了解仓库中同族的 useSwipeEvents、useTouch 与 useTouchEvents 文档。
- 前端
- 开发工具
【免费下载链接】beautiful-react-hooks
🔥 A collection of beautiful and (hopefully) useful React hooks to speed-up your components and hooks development 🔥
相关推荐
beautiful-react-hooks 中的 useHorizontalSwipe:横跨桌面与移动端的横向滑动手势 Hook
beautiful react hooks 中的 useHorizontalSwipe:横跨桌面与移动端的横向滑动手势 Hook 在 beautiful rea
前端开发工具跨平台资源嗅探工具res-downloader:三步解决网络资源获取难题
跨平台资源嗅探工具res downloader:三步解决网络资源获取难题 在数字内容创作日益普及的今天,你是否曾为获取无水印视频素材而烦恼?或者因无法批量下载在
桌面应用网络音视频QQ空间历史说说还能找回来吗:GetQzonehistory扫码一次,5分钟恢复十年存档
QQ空间历史说说还能找回来吗:GetQzonehistory扫码一次,5分钟恢复十年存档 QQ官方从未提供批量导出入口,你发过多年的说说,正悄悄散落在一个没有"
网页爬虫数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考