VueUse useMousePressed:响应式鼠标/触摸按下状态检测完整指南
VueUse useMousePressed:响应式鼠标/触摸按下状态检测完整指南
useMousePressed 是 VueUse(Vue 3 组合式工具集)中位于 packages/core/useMousePressed 的核心传感器(Sensors)类组合式函数,用于响应式地检测鼠标按键、触摸以及拖拽的按下与释放状态。它基于目标元素上的 mousedown/touchstart 事件触发按下,并在 window 上的 mouseup/mouseleave/touchend/touchcancel 等事件触发释放,返回一个响应式的 pressed 布尔值与来源类型 sourceType。读完本文,你将掌握该函数的全部配置项、事件触发/释放机制、如何限定监听目标、组件式用法(UseMousePressed),以及其底层源码实现与测试验证逻辑,可直接用于手势按钮、按住状态高亮、拖拽判定等交互场景。
快速上手:最基本的按下状态检测
从 @vueuse/core 直接导入 useMousePressed,解构出 pressed 响应式状态即可:
import { useMousePressed } from '@vueuse/core'
const { pressed } = useMousePressed()
默认情况下,只要鼠标在页面上被按下、或手指在触摸屏上按下,pressed 就会变为 true;鼠标松开、指针离开窗口、触摸结束或触摸被系统取消时恢复为 false。你可以在模板或计算属性中直接消费这个状态,例如实现"按住高亮"或"按住录音"等交互。
useMousePressed 返回一个包含两个属性的对象,类型定义见 index.ts:
| 返回值 | 类型 | 说明 |
|---|---|---|
pressed |
ShallowRef<boolean> |
当前是否处于按下状态 |
sourceType |
ShallowRef<UseMouseSourceType> |
触发来源,取值为 'mouse'、'touch' 或 null |
其中 UseMouseSourceType 定义于 useMouse/index.ts,与 useMouse 共用同一类型。
完整配置项详解
useMousePressed 接受一个可选参数对象 UseMousePressedOptions(定义见 index.ts),下表汇总了所有配置项及其默认值:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
touch |
boolean |
true |
是否监听 touchstart/touchend 等触摸事件 |
drag |
boolean |
true |
是否监听 dragstart/drop/dragend 等拖拽事件 |
capture |
boolean |
false |
是否以捕获阶段(capture)注册事件监听器 |
initialValue |
boolean |
false |
pressed 的初始值 |
target |
MaybeComputedElementRef |
window |
限定触发按下事件的目标元素(可为 ref / getter / DOM 元素) |
window |
Window |
defaultWindow |
自定义 window 实例,适用于 iframe 或测试环境(继承自 ConfigurableWindow,见 _configurable.ts) |
onPressed |
(event: MouseEvent | TouchEvent | DragEvent) => void |
无 | 按下时触发的回调,接收原始事件对象 |
onReleased |
(event: MouseEvent | TouchEvent | DragEvent) => void |
无 | 释放时触发的回调,接收原始事件对象 |
仅检测鼠标、关闭触摸检测
默认开启触摸检测(touch: true)。如果只希望响应鼠标操作(例如纯桌面端的长按场景),显式关闭触摸:
import { useMousePressed } from '@vueuse/core'
const { pressed } = useMousePressed({ touch: false })
初始值
某些场景下需要状态从 true 开始(例如组件挂载时即视为已按下),通过 initialValue 指定:
const { pressed } = useMousePressed({ initialValue: true })
测试用例 index.browser.test.ts 同时验证了默认初始值为 false 与自定义初始值两种情形。
事件回调
监听按下与释放事件本身,而不必手动 watch:
useMousePressed({
onPressed: (event) => {
console.log('pressed', event.type)
},
onReleased: (event) => {
console.log('released', event.type)
},
})
测试中通过 vi.fn() 验证了 mousedown 触发 onPressed、mouseup 触发 onReleased 的行为(见 index.browser.test.ts)。
事件机制:谁触发按下、谁触发释放
从源码 index.ts 可以清晰看到事件配对逻辑:按下事件绑定在目标元素上,释放事件一律绑定在 window 上,这保证了即使指针移出目标元素,只要在窗口内松开也能正确复位状态。
| 来源 | 按下(绑定 target) | 释放(绑定 window) |
|---|---|---|
| 鼠标 | mousedown |
mouseup、mouseleave |
| 拖拽 | dragstart |
drop、dragend |
| 触摸 | touchstart |
touchend、touchcancel |
各事件监听器使用 { passive: true, capture } 作为注册选项(见 index.ts),passive 保证滚动性能不被事件处理阻塞,capture 是否开启则由配置项 capture 控制。
鼠标与触摸还做了来源区分:按下时把 sourceType 置为 'mouse' 或 'touch',释放时重置为 null(见 index.ts)。如果你想区分"这次按下是鼠标还是手指",可以直接读取 sourceType:
const { pressed, sourceType } = useMousePressed()
watchEffect(() => {
if (pressed.value) {
console.log(sourceType.value === 'touch' ? '触屏按下' : '鼠标按下')
}
})
拖拽感知(drag 选项)
drag: true(默认)时额外监听拖拽事件:元素上触发 dragstart 视为按下,窗口上的 drop/dragend 视为释放。这让你可以把"开始拖拽"也统一纳入按下状态判断,很适合做拖拽中的样式反馈。对应的测试覆盖了 dragstart→drop 与 dragstart→dragend 两条链路(见 index.browser.test.ts)。
限定目标元素:只响应指定区域的按下
默认监听 window,页面任意位置按下都会触发。若只想在特定元素(按钮、卡片、画布区域)上触发,传入 target。target 的类型是 MaybeComputedElementRef,支持 DOM 元素、ref、getter 或计算属性,传入后内部会先经过 unrefElement 归一化(见 index.ts 与 unrefElement/index.ts)。
在 <script setup> 中使用 useTemplateRef 获取模板元素引用:
<script setup lang="ts">
import { useMousePressed } from '@vueuse/core'
import { useTemplateRef } from 'vue'
const el = useTemplateRef('el')
const { pressed } = useMousePressed({ target: el })
</script>
<template>
<div ref="el">
只有点击这个元素才会触发状态更新。
</div>
</template>
当 target 未传或解析为空时,源码会自动回退为 window(unrefElement(options.target) || window),因此省略 target 即为全窗口监听。
在线示例 demo.vue 还演示了一个进阶玩法:用一个 useToggle 控制 target 在"当前区块元素"与 window 之间切换,按下状态会以 YAML 形式实时渲染出来,方便直观对比两种监听范围的行为差异。
组件用法:UseMousePressed
除了组合式函数形式,同目录还提供了渲染无关的组件版 UseMousePressed(实现见 component.ts)。它把 useMousePressed 的返回对象通过作用域插槽暴露出来,组件自身渲染为包裹元素并把自身 DOM 作为默认 target,因此"只在组件区域内按下"无需手动传 ref:
<template>
<UseMousePressed v-slot="{ pressed }">
Is Pressed: {{ pressed }}
</UseMousePressed>
</template>
该组件支持 as 属性自定义渲染的标签(默认 div),并透传 capture、drag、initialValue、onPressed、onReleased、touch、window 等全部函数版选项(见 component.ts)。注意 target 不在组件 props 中(通过 Omit<UseMousePressedOptions, 'target'> 剔除),因为组件的根元素本身就是目标。
源码实现深度解析
核心实现非常精简,全部逻辑在 index.ts 的 useMousePressed 函数中(约 130 行):
- 解构默认值:
touch = true、drag = true、capture = false、initialValue = false、window = defaultWindow(见 index.ts)。 - 创建状态:
pressed与sourceType均为shallowRef,前者初始化为initialValue,后者初始化为null。使用 shallowRef 而非 ref,是因为这两个值是简单的布尔/字符串标量,浅层响应即可避免不必要的深层响应式开销。 - SSR 安全:若
window为undefined(服务端渲染或无 window 环境),直接返回初始状态,不做任何事件绑定(见 index.ts)。defaultWindow的定义见 _configurable.ts,仅在客户端环境(isClient)下取window。 - 内部事件处理:
onPressed通过柯里化闭包接收来源类型,按下时同时更新pressed、sourceType并调用用户回调;onReleased则复位两者并调用回调(见 index.ts)。 - 事件绑定:全部通过
useEventListener完成,按下事件挂在target(解析后的元素或 window)上,释放事件挂在window上,并按drag/touch开关决定是否注册对应事件组(见 index.ts)。
测试验证
packages/core/useMousePressed/index.browser.test.ts 是 Vitest 浏览器环境测试,完整覆盖了:
- 函数存在性与默认返回值(
pressed: false、sourceType: null); - 自定义
initialValue; - 无 target 时
window.dispatchEvent(new Event('mousedown'))触发按下; - 有 target 时向目标元素派发
dragstart也能触发按下; - 鼠标、拖拽、触摸三组"触发/释放"事件配对(
mouseup、mouseleave、drop、dragend、touchend、touchcancel)全部验证通过。
这些用例直接印证了上文的"按下绑定 target、释放绑定 window"的架构,也验证了 sourceType 在不同事件来源下的取值。
常见用法组合建议
- 按住执行 + 松手停止:
watch(pressed, ...)或在onPressed/onReleased中启停定时器,可快速实现按住连击、按住录音。 - 区域限制:配合
target只监听某个按钮/画布,避免全局误触。 - 长按判定:结合
useTimeoutFn,在onPressed中启动计时、onReleased中清除,可实现长按/短按区分。 - 与 useMouse 配合:
useMousePressed的sourceType与useMouse的sourceType类型一致,两函数可搭配使用以同时获得指针坐标与按下状态。
该函数已通过 packages/core/index.ts 从 @vueuse/core 统一导出,可以直接引入使用,无需单独安装子包。