☰
VueUse usePointer 完全指南:Vue 3 中的响应式指针状态监听与指针事件处理
2026/10/1 22:06:39 网站建设 项目流程
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

导读

usePointer是 VueUse(@vueuse/core)在 Sensors(传感器)分类下提供的一个组合式函数,它将浏览器 Pointer Events(指针事件) 展开,并结合 index.ts、component.ts 与测试用例,讲解其全部返回字段、配置参数、组件式用法以及底层实现原理,读完后你可以直接在任意 Vue 3 项目中接入指针追踪能力,实现绘图、悬停高亮、触摸交互等场景。

快速上手:基本用法

usePointer的默认行为是跟踪window全局窗口上的指针状态。引入并使用它只需要三行代码:

import { usePointer } from '@vueuse/core' const { x, y, pressure, pointerType } = usePointer()

解构出的x、y、pressure、pointerType均为 Vue 响应式引用(Ref),它们会在指针事件触发时自动更新。官方文档中给出的这一段 demo.vue 展示了最直观的调试方式——把整个响应式对象打印在页面上:

<script setup lang="ts"> import { usePointer } from '@vueuse/core' import { reactive } from 'vue' const pointer = reactive(usePointer()) </script> <template> <pre class="select-none" style="touch-action: none">{{ pointer }}</pre> </template>

注意:demo 中给<pre>添加了touch-action: none,这是指针事件(尤其是触摸场景)下的常见实践,用于避免浏览器默认的滚动/缩放手势抢占指针事件,保证pointermove能持续触发。

返回值详解:一次拿到完整的指针状态

usePointer返回一个对象,其中既包含文档示例中提到的x、y、pressure、pointerType,也包含更多可用于精细化交互的字段。根据源码 index.ts 中UsePointerReturn接口定义,完整返回结构如下:

字段类型含义
xRef<number>指针相对目标(默认为窗口)的水平坐标
yRef<number>指针相对目标(默认为窗口)的垂直坐标
pressureRef<number>指针压力值,范围0到1(鼠标通常为0.5,无压感设备为0或0.5)
pointerIdRef<number>产生当前事件的指针的唯一标识符
tiltXRef<number>指针(如触控笔)在 X 轴的倾斜角,范围-90到90度
tiltYRef<number>指针在 Y 轴的倾斜角,范围-90到90度
widthRef<number>指针接触区域的宽度(CSS 像素)
heightRef<number>指针接触区域的高度(CSS 像素)
twistRef<number>指针围绕自身主轴的顺时针旋转角,范围0到359度(部分触控笔支持)
pointerTypeRef<PointerType \| null>指针类型:'mouse'、'touch'或'pen'
isInsideShallowRef<boolean>指针是否处于目标区域内部

其中PointerType联合类型定义在 types.ts:

export type PointerType = 'mouse' | 'touch' | 'pen'

初始状态在源码 index.ts 的defaultState中定义:坐标、压力、倾斜角、尺寸、旋转角均为0,pointerType为null。也就是说,在用户第一次触发指针事件之前,你拿到的x/y等值是0,而非undefined,这保证了状态始终可安全读取。

对于大多数桌面鼠标场景,tiltX/tiltY/twist会保持0或由浏览器给出默认值;这些字段主要面向触控笔(pen)输入,比如手写板应用中区分笔的倾斜与旋转姿态。

配置项详解:定制你的监听行为

usePointer接受一个可选的options对象。根据UsePointerOptions接口(index.ts),支持以下配置:

配置项类型默认值说明
targetMaybeRef<EventTarget \| null \| undefined> \| Document \| Windowwindow监听指针事件的目标对象,可为窗口、文档或任意 DOM 元素(含 Ref)
pointerTypesPointerType[]['mouse', 'touch', 'pen']需要监听的指针类型白名单,仅匹配的类型会更新状态
initialValueMaybeRef<Partial<UsePointerState>>{}(与默认值合并)初始状态值,用于自定义指针尚未移动时的默认坐标等
windowWindow全局window指定自定义window实例(来自ConfigurableWindow,见 _configurable.ts),适用于 iframe 或测试环境

监听特定元素:target

默认target为defaultWindow(客户端环境即全局window,见 _configurable.ts)。如果要跟踪某个元素内部的局部指针位置,把target设为该元素的 Ref 即可:

<script setup lang="ts"> import { usePointer } from '@vueuse/core' import { useTemplateRef } from 'vue' const el = useTemplateRef<HTMLElement>('board') const { x, y, isInside } = usePointer({ target: el }) </script> <template> <div ref="board" style="position: relative; width: 300px; height: 200px"> 指针坐标:{{ x }}, {{ y }}|是否在区域内:{{ isInside }} </div> </template>

过滤指针类型:pointerTypes

pointerTypes用于只响应特定输入设备。例如只想处理触控笔(pen)的压感与倾斜数据、忽略鼠标:

const { pressure, tiltX, tiltY } = usePointer({ pointerTypes: ['pen'] })

源码在 index.ts 中实现了该过滤逻辑:事件触发后,先检查options.pointerTypes是否存在,若存在且不包含event.pointerType则直接返回,不更新状态。

自定义初始值:initialValue

initialValue允许你指定指针未移动前的默认状态。源码 index.ts 的实现是先以defaultState兜底,再用用户传入的值覆盖合并:

const state = shallowRef(options.initialValue || {}) as unknown as Ref<UsePointerState> Object.assign(state.value, defaultState, state.value)

因此你可以只覆盖部分字段,例如:

const { x, y } = usePointer({ initialValue: { x: 100, y: 100 } })

源码级原理:usePointer是如何工作的

理解了用法后,深入 index.ts 能帮你彻底掌握其行为边界。核心实现只有约 30 行,逻辑非常清晰:

1. 统一事件处理器

handler负责处理三种“激活”事件pointerdown、pointermove、pointerup(index.ts):

const handler = (event: PointerEvent) => { isInside.value = true if (options.pointerTypes && !options.pointerTypes.includes(event.pointerType as PointerType)) return state.value = objectPick(event, keys, false) as UsePointerState }

关键点在于objectPick(event, keys, false):它把PointerEvent上的x/y/pressure/pointerId/tiltX/tiltY/width/height/twist/pointerType这些字段批量抽取到状态对象中。objectPick是 VueUse 的工具函数,定义在 packages/shared/utils/general.ts,其实现是keys.reduce逐键拷贝,keys则来源于defaultState的键集合(index.ts),保证抽取字段与状态结构完全一致。

2. 生命周期与事件绑定

事件绑定交由useEventListener完成(index.ts):

if (target) { const listenerOptions = { passive: true } useEventListener(target, ['pointerdown', 'pointermove', 'pointerup'], handler, listenerOptions) useEventListener(target, ['pointerleave', 'pointercancel'], () => isInside.value = false, listenerOptions) }
  • 监听器以{ passive: true }注册,避免阻塞滚动,同时也就意味着不能在其中调用preventDefault(),需要阻止默认行为时应自行在元素上单独绑定事件;
  • useEventListener(见 packages/core/useEventListener/index.ts)会在组件挂载时自动addEventListener、卸载时自动removeEventListener,因此usePointer无需手动清理;
  • 当target为空(如 SSR 环境下defaultWindow为undefined)时,整个监听逻辑被跳过,函数依然可以安全调用。

3.isInside的语义与边界

isInside用于表达“指针是否在目标区域内”:

  • pointerdown / pointermove / pointerup→ 置为true;
  • pointerleave / pointercancel→ 置为false。

需要留意一个边界:pointerleave并不总是触发,例如在多点触控中第二个触点发起捏合手势时,用户代理会直接发送pointercancel而可能不再派发pointerleave。测试文件 index.test.ts 专门覆盖了这个场景——这也是usePointer同时监听pointercancel的原因,确保手势被系统接管时isInside仍能及时复位。

组件式用法:<UsePointer>

除了组合式函数,VueUse 还提供了同名渲染组件<UsePointer>,其实现位于 component.ts。组件默认在window上跟踪指针,并通过v-slot将全部响应式状态注入插槽:

<template> <UsePointer v-slot="{ x, y }"> x: {{ x }} y: {{ y }} </UsePointer> </template>

跟踪元素内局部位置:target="self"

组件版的target被收窄为两个字符串取值(见 component.ts 的UsePointerProps):

<template> <UsePointer v-slot="{ x, y }" target="self"> x: {{ x }} y: {{ y }} </UsePointer> </template>

target="self"时,组件会把指针跟踪目标指向自身所在的 DOM 元素,因此x/y表示指针相对该元素左上角的局部坐标。源码中的实现方式是:组件内部维护一个shallowRef<HTMLElement | null>(null)作为元素引用,当props.target === 'self'时把它作为target传给usePointer,否则使用defaultWindow(component.ts):

const el = shallowRef<HTMLElement | null>(null) const data = reactive(usePointer({ ...props, target: props.target === 'self' ? el : defaultWindow, }))

组件接收的 props 与组合式函数的 options 完全对应:initialValue、pointerTypes、target、window。需要注意的是,组件返回的是通过reactive()包装后的数据,因此插槽解构出的每个字段都已经具备响应式追踪能力,模板中可直接绑定。

综合实战:可复用的指针绘图/悬停面板

把以上知识整合起来,下面是一个实用的“局部坐标 + 类型过滤 + 边界提示”组合示例,同时用到了组合式 API 的关键特性:

<script setup lang="ts"> import { usePointer } from '@vueuse/core' import { useTemplateRef } from 'vue' const board = useTemplateRef<HTMLElement>('board') const { x, y, pressure, pointerType, isInside } = usePointer({ target: board, pointerTypes: ['mouse', 'pen'], // 忽略触摸,便于统一处理 }) </script> <template> <div ref="board" style="width: 100%; height: 300px; touch-action: none; border: 1px solid #888; position: relative;" > <span v-if="isInside"> 类型:{{ pointerType }} | 坐标:({{ Math.round(x) }}, {{ Math.round(y) }}) | 压力:{{ pressure.toFixed(2) }} </span> <span v-else>将指针移入区域</span> </div> </template>
  • touch-action: none防止触摸滚动干扰pointermove的连续触发;
  • 通过pointerTypes过滤掉触摸事件后,pressure等字段的来源更可控;
  • isInside直接驱动“是否在区域内”的提示文案,替代繁琐的手动移入/移出判断。

使用注意事项与适用前提

  • 环境前提:usePointer依赖浏览器的 Pointer Events API,属于客户端能力。在 SSR(服务端渲染)环境下,defaultWindow为undefined,事件监听会被跳过,函数可安全执行但状态保持初始值;
  • 事件不冒泡时:当target是具体元素时,只有发生在该元素及其子树内的指针事件才会被捕获(取决于事件冒泡),页面其他区域的指针移动不会更新状态;
  • passive: true限制:底层监听器以 passive 模式注册,不要在usePointer的响应回调里调用preventDefault(),否则会收到浏览器警告且无效;
  • 多点触控:pointerId可区分不同的触点,若需分别追踪多个触点,建议基于usePointer自行维护触点映射,或组合使用指针捕获相关 API。

结语

usePointer以极小的 API 面(一个函数、一个组件、三组配置项)完整封装了指针事件的常用字段,返回值既覆盖x/y坐标这种高频需求,也涵盖pressure/tiltX/tiltY/twist/width/height等进阶数据,配合isInside与pointerTypes过滤,可以应对悬停提示、局部坐标定位、压感绘图、触控笔姿态识别等多种交互场景。若需进一步研究其实现细节,可重点阅读 index.ts、component.ts、index.test.ts 以及事件绑定的底层依赖 useEventListener。

  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载
上一篇:Node.js 11.8.0(Current)版本发布深度解析:核心亮点、提交清单与发布机制
下一篇:OpenProject 外部 Wiki 提供方(External Wiki Providers)集成指南:XWiki 配置、OAuth 2.0 认证与运维管理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询