- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
导读
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接口定义,完整返回结构如下:
| 字段 | 类型 | 含义 |
|---|---|---|
x | Ref<number> | 指针相对目标(默认为窗口)的水平坐标 |
y | Ref<number> | 指针相对目标(默认为窗口)的垂直坐标 |
pressure | Ref<number> | 指针压力值,范围0到1(鼠标通常为0.5,无压感设备为0或0.5) |
pointerId | Ref<number> | 产生当前事件的指针的唯一标识符 |
tiltX | Ref<number> | 指针(如触控笔)在 X 轴的倾斜角,范围-90到90度 |
tiltY | Ref<number> | 指针在 Y 轴的倾斜角,范围-90到90度 |
width | Ref<number> | 指针接触区域的宽度(CSS 像素) |
height | Ref<number> | 指针接触区域的高度(CSS 像素) |
twist | Ref<number> | 指针围绕自身主轴的顺时针旋转角,范围0到359度(部分触控笔支持) |
pointerType | Ref<PointerType \| null> | 指针类型:'mouse'、'touch'或'pen' |
isInside | ShallowRef<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),支持以下配置:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
target | MaybeRef<EventTarget \| null \| undefined> \| Document \| Window | window | 监听指针事件的目标对象,可为窗口、文档或任意 DOM 元素(含 Ref) |
pointerTypes | PointerType[] | ['mouse', 'touch', 'pen'] | 需要监听的指针类型白名单,仅匹配的类型会更新状态 |
initialValue | MaybeRef<Partial<UsePointerState>> | {}(与默认值合并) | 初始状态值,用于自定义指针尚未移动时的默认坐标等 |
window | Window | 全局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
相关推荐
PyPTO Tensor 操作全指南:数学运算与逻辑结构变换实战
PyPTO Tensor 操作全指南:数学运算与逻辑结构变换实战 PyPTO 作为 Parallel Tensor/Tile Operation 编程范式,为昇
前端Airi 实战:VueUse useEventListener 响应式事件监听完整指南
Airi 实战:VueUse useEventListener 响应式事件监听完整指南 导读 useEventListener 是 VueUse 提供的浏览器事
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染VueUse useScroll 完整指南:响应式滚动位置、边界状态与指令化监听
VueUse useScroll 完整指南:响应式滚动位置、边界状态与指令化监听 useScroll 是 VueUse(Vue 3 组合式工具库)核心包中位于
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考