☰
VueUse usePointerLock:基于 Pointer Lock API 的反应式指针锁定 Composable 全解析
2026/10/1 2:24:32 网站建设 项目流程
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

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

导读

usePointerLock是 VueUse 核心包中面向Pointer Lock API的响应式封装组合式函数(Composable)。在 3D 游戏、第一人称视角漫游、拖拽画布等场景中,我们需要把鼠标指针"隐藏"并持续捕获其位移增量,而 Pointer Lock API 正是浏览器提供的标准能力。读完本文,你将掌握usePointerLock的完整 API、Composable 与组件两种用法、底层实现原理(事件监听、锁定时序、错误处理),以及它在 VueUse 官方 demo(3D 立方体旋转)中的真实落地方式。

一、快速上手:Composable 用法

usePointerLock通过 VueUse 的入口统一导出(见 packages/core/index.ts),使用方式非常简洁:

import { usePointerLock } from '@vueuse/core' const { isSupported, lock, unlock, element, triggerElement, } = usePointerLock()

返回值一览

返回值类型说明
isSupportedComputedRef<boolean>当前浏览器环境是否支持 Pointer Lock API
lock(e: MaybeElementRef \| Event) => Promise<MaybeElement>发起指针锁定,返回 Promise,锁定成功后 resolve 为被锁定的元素
unlock() => Promise<boolean>解除指针锁定,成功返回true
elementShallowRef<MaybeElement>当前被锁定的 DOM 元素(未锁定时为null)
triggerElementShallowRef<MaybeElement>触发锁定的元素(如按钮、画布)

其中MaybeElement的类型定义位于 unrefElement/index.ts,指HTMLElement | SVGElement | VueInstance | undefined | null;MaybeElementRef即MaybeRef<T>,既可以是元素本身,也可以是元素对应的 ref。

二、Component 组件用法

如果更倾向于模板语法,VueUse 还提供了对应的UsePointerLock渲染组件(实现见 usePointerLock/component.ts),通过作用域插槽(scoped slot)暴露同样的响应式数据:

<template> <UsePointerLock v-slot="{ lock }"> <canvas /> <button @click="lock"> Lock Pointer on Canvas </button> </UsePointerLock> </template>

组件支持两个 props:

  • as:默认渲染为div,可通过该 prop 指定要渲染的标签名(如canvas);
  • document:自定义 document 实例,用于 iframe 或测试环境。

组件内部会把usePointerLock(target)的返回值用reactive()包裹后注入默认插槽,同时给根元素绑定targetref,这样lock事件(如@click="lock")会以 Event 形式传入,自动将根元素识别为锁定目标。

三、锁定与解锁:lock/unlock的完整时序

1.lock:发起并等待锁定成功

async function lock(e: MaybeElementRef | Event) { if (!isSupported.value) throw new Error('Pointer Lock API is not supported by your browser.') triggerElement.value = e instanceof Event ? <HTMLElement>e.currentTarget : null targetElement = e instanceof Event ? unrefElement(target) ?? triggerElement.value : unrefElement(e) if (!targetElement) throw new Error('Target element undefined.') targetElement.requestPointerLock() return await until(element).toBe(targetElement) }

从源码(usePointerLock/index.ts)可以看出lock同时支持两类入参:

  • 传入事件对象(Event):例如在模板中@click="lock",此时currentTarget会被记入triggerElement,锁定目标优先取 composable 初始化时传入的target,否则回退到triggerElement;
  • 传入元素或元素 ref(MaybeElementRef):直接以unrefElement(e)作为锁定目标。

确认目标元素后调用原生requestPointerLock(),随后借助until(element).toBe(targetElement)(来自 @vueuse/shared 的 until)等待pointerlockchange事件把element更新为目标元素——也就是说,lock返回的 Promise 会在锁定真正生效后才 resolve,而不是调用后立即返回。

2.unlock:解除锁定

async function unlock() { if (!element.value) return false document!.exitPointerLock() await until(element).toBeNull() return true }

当没有元素处于锁定状态时直接返回false;否则调用原生document.exitPointerLock(),并等待element变回null(即锁定解除生效)后返回true。

四、底层原理:事件监听与状态同步

usePointerLock的响应式状态完全由两个原生事件驱动,事件监听逻辑见 usePointerLock/index.ts,均使用{ passive: true }监听选项:

  • pointerlockchange:指针锁定状态发生变化时触发。源码用document.pointerLockElement ?? element.value取当前锁定元素;只有当targetElement存在且与当前锁定元素一致时,才把document.pointerLockElement同步到element.value;若锁定已解除,则将targetElement与triggerElement一并清空为null。
  • pointerlockerror:获取或释放锁失败时触发。此时根据document.pointerLockElement是否还存在判断是「获取失败(acquire)」还是「释放失败(release)」,并抛出对应的错误信息。

值得注意的边界情况:当调用lock时传入了target(即usePointerLock(target)初始化参数),后续任何元素的pointerlockchange事件都不会影响本 composable 的状态,只有target相关的锁变化才会被同步——这也是源码中if (targetElement && currentElement === targetElement)判断的用意。

五、能力检测:isSupported与useSupported

isSupported通过 VueUse 的通用能力检测函数useSupported(实现见 useSupported/index.ts)计算得出:

const isSupported = useSupported(() => document && 'pointerLockElement' in document)
  • useSupported内部先调用useMounted确保在组件挂载后才计算;
  • 检测逻辑为「存在 document 且其上有pointerLockElement属性」,这等价于判断浏览器是否实现 Pointer Lock API。

因此usePointerLock是完全 SSR 安全的:服务端渲染时defaultDocument为undefined(见 core/_configurable.ts),isSupported为false,事件监听与锁定逻辑都不会执行。

六、官方 Demo 实战:3D 立方体旋转

VueUse 为usePointerLock提供了可视化 demo(usePointerLock/demo.vue),演示了指针锁定在 3D 场景中最典型的使用方式——第一人称/轨道旋转控制:

<script setup lang="ts"> import { useMouse, usePointerLock } from '@vueuse/core' import { shallowRef, watch } from 'vue' const { lock, unlock, element } = usePointerLock() const { x, y } = useMouse({ type: 'movement' }) const rotY = shallowRef(-45) const rotX = shallowRef(0) watch([x, y], ([x, y]) => { if (!element.value) return rotY.value += x / 2 rotX.value -= y / 2 }) </script>

关键思路:

  1. usePointerLock()返回lock/unlock/element;
  2. useMouse({ type: 'movement' })只跟踪鼠标位移增量(movementX/movementY),指针锁定后该数据即代表旋转角度的变化;
  3. 模板中在立方体容器上绑定@mousedown.capture="lock"与@mouseup="unlock"——按下时锁定指针,松开时解除锁定,形成"按住拖动旋转"的交互;
  4. watch([x, y])内先检查element.value是否存在,只有确实处于锁定状态时才累加旋转角rotY/rotX,避免未锁定时的误旋转。

对应的浏览器测试 usePointerLock/demo.browser.test.ts 验证了 demo 的渲染以及 hover / click 不会抛出错误,可作为接入测试的参考。

七、配置选项与适用场景总结

Options

usePointerLock的选项继承ConfigurableDocument(见 core/_configurable.ts):

export interface UsePointerLockOptions extends ConfigurableDocument { // pointerLockOptions?: PointerLockOptions }
  • document?: Document:自定义 document 实例,典型用于iframe 环境(锁定发生在 iframe 内)或测试环境(注入 jsdom 等 mock document)。默认取window.document(客户端)。

典型适用场景

  • 3D 场景 / 游戏中的鼠标视角控制(如官方 demo 的立方体旋转);
  • 全屏绘图、白板类应用中需要隐藏光标并持续跟踪位移的场景;
  • 任何需要「捕获鼠标指针并消除系统光标」的沉浸式交互。

使用注意事项

  1. 浏览器兼容性:先检查isSupported.value,为不支持的浏览器提供降级方案;
  2. 锁定元素必须可见:Pointer Lock API 要求目标元素在文档中可见,且锁定请求通常需要发生在用户手势(如 click / mousedown)处理函数内,因此 demo 中使用@mousedown.capture很关键;
  3. Esc 键自动退出:用户按Esc或系统会自动解除指针锁定,此时pointerlockchange会触发,element会被清空,业务侧应监听该变化(如暂停游戏循环);
  4. 异步时序:lock/unlock返回的 Promise 会等待状态真正切换完成,可利用await保证后续逻辑的时序正确性。

八、小结

usePointerLock以约 100 行源码(usePointerLock/index.ts)完整封装了 Pointer Lock API 的能力检测、事件监听、锁定/解锁时序与响应式状态同步,并提供 Composable 与UsePointerLock组件两种使用形式。无论是游戏视角控制还是沉浸式绘图,它都能让你用最少的代码获得类型安全、SSR 安全且可测试的指针锁定能力——这也是 VueUse "Essential Vue Composition Utilities" 设计理念的一个典型缩影。

  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

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

相关推荐

上一篇:如何高效配置跨平台网盘直链解析工具:技术实现与实战指南
下一篇:10 条命令搞定 Linux 压缩解压:tar、gzip、zip 高频组合实战

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

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

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

立即咨询