Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(usePointerLock): new function (#2590)
* feat(usePointerLock): new function * feat(useMouse): support for movement detection * fix(usePointerLock): update demo * fix(usePointerLock): use event.currentTarget instead of event.target * fix(usePointerLock): update demo * fix(usePointerLock): fix pending element tracking when usePointerLock used in multiple components * fix(usePointerLock): unlock fix * feat(usePointerLock): simplify demo * fix(usePointerLock): reset targetElement on pointer lock release even if release is not caused by the user * feat(usePointerLock): keep track of element which triggered pointer lock (for cases where pointer lock is acquired on a different element) * feat(usePointerLock): support unadjusted mouse movement tracking (experimental) * feat(usePointerLock): migrate demo to unocss Co-authored-by: Sergey Danilchenko <s.danilchenko@ttbooking.ru> Co-authored-by: Jelf <353742991@qq.com> Co-authored-by: wheat <jacobrclevenger@gmail.com>
- Loading branch information
1 parent
691622a
commit ae69fb8
Showing
6 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { defineComponent, h, reactive, ref } from 'vue-demi' | ||
import { usePointerLock } from '@vueuse/core' | ||
import type { RenderableComponent } from '../types' | ||
|
||
export const UsePointerLock = defineComponent<RenderableComponent>({ | ||
name: 'UsePointerLock', | ||
props: ['as'] as unknown as undefined, | ||
setup(props, { slots }) { | ||
const target = ref() | ||
const data = reactive(usePointerLock(target)) | ||
|
||
return () => { | ||
if (slots.default) | ||
return h(props.as || 'div', { ref: target }, slots.default(data)) | ||
} | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<script setup> | ||
import { ref, watch } from 'vue-demi' | ||
import { useMouse, usePointerLock } from '@vueuse/core' | ||
const { lock, unlock, element } = usePointerLock() | ||
const { x, y } = useMouse({ type: 'movement' }) | ||
const rotY = ref(-45) | ||
const rotX = ref(0) | ||
watch([x, y], ([x, y]) => { | ||
if (!element.value) | ||
return | ||
rotY.value += x / 2 | ||
rotX.value -= y / 2 | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div scene> | ||
<div cube @mousedown.capture="lock" @mouseup="unlock"> | ||
<span face base style="--i: 1" logo-vue /> | ||
<span face base style="--i: -1" logo-vueuse /> | ||
<span face side style="--i: 0" logo-vue /> | ||
<span face side style="--i: 1" logo-vueuse /> | ||
<span face side style="--i: 2" logo-vue /> | ||
<span face side style="--i: 3" logo-vueuse /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style scoped lang="postcss"> | ||
[scene] { | ||
@apply flex justify-center items-center box-border perspective-300; | ||
} | ||
[cube] { | ||
@apply cursor-all-scroll relative w-100px h-100px preserve-3d; | ||
--rotY: v-bind(rotY); | ||
--rotX: v-bind(rotX); | ||
transform: rotateY(calc(var(--rotY) * 1deg)) rotateX(calc(var(--rotX) * 1deg)); | ||
} | ||
[face] { | ||
@apply absolute top-0 left-0 w-full h-full b-1 b-solid backface-hidden | ||
bg-emerald-4 bg-opacity-20 bg-center bg-[length:75%] bg-no-repeat; | ||
} | ||
[base] { | ||
transform: rotateX(calc(90deg * var(--i))) translateZ(50px); | ||
} | ||
[side] { | ||
transform: rotateY(calc(90deg * var(--i))) translateZ(50px); | ||
} | ||
[logo-vue] { | ||
@apply bg-[url(/vue.svg)]; | ||
} | ||
[logo-vueuse] { | ||
@apply bg-[url(/favicon.svg)]; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
category: Sensors | ||
--- | ||
|
||
# usePointerLock | ||
|
||
Reactive [pointer lock](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API). | ||
|
||
## Basic Usage | ||
|
||
```js | ||
import { usePointerLock } from '@vueuse/core' | ||
|
||
const { isSupported, lock, unlock, element, triggerElement } = usePointerLock() | ||
``` | ||
|
||
## Component Usage | ||
|
||
```html | ||
<UsePointerLock v-slot="{ lock }"> | ||
<canvas /> | ||
<button @click="lock"> | ||
Lock Pointer on Canvas | ||
</button> | ||
</UsePointerLock> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { ref } from 'vue-demi' | ||
import { until } from '@vueuse/shared' | ||
import { useEventListener } from '../useEventListener' | ||
import { useSupported } from '../useSupported' | ||
import { unrefElement } from '../unrefElement' | ||
import type { MaybeElementRef } from '../unrefElement' | ||
import type { ConfigurableDocument } from '../_configurable' | ||
import { defaultDocument } from '../_configurable' | ||
|
||
declare global { | ||
interface PointerLockOptions { | ||
unadjustedMovement?: boolean | ||
} | ||
|
||
interface Element { | ||
requestPointerLock(options?: PointerLockOptions): Promise<void> | void | ||
} | ||
} | ||
|
||
type MaybeHTMLElement = HTMLElement | undefined | null | ||
|
||
export interface UsePointerLockOptions extends ConfigurableDocument { | ||
pointerLockOptions?: PointerLockOptions | ||
} | ||
|
||
/** | ||
* Reactive pointer lock. | ||
* | ||
* @see https://vueuse.org/usePointerLock | ||
* @param target | ||
* @param options | ||
*/ | ||
export function usePointerLock(target?: MaybeElementRef<MaybeHTMLElement>, options: UsePointerLockOptions = {}) { | ||
const { document = defaultDocument, pointerLockOptions } = options | ||
|
||
const isSupported = useSupported(() => document && 'pointerLockElement' in document) | ||
|
||
const element = ref<MaybeHTMLElement>() | ||
|
||
const triggerElement = ref<MaybeHTMLElement>() | ||
|
||
let targetElement: MaybeHTMLElement | ||
|
||
if (isSupported.value) { | ||
useEventListener(document, 'pointerlockchange', () => { | ||
const currentElement = document!.pointerLockElement ?? element.value | ||
if (targetElement && currentElement === targetElement) { | ||
element.value = document!.pointerLockElement as MaybeHTMLElement | ||
if (!element.value) | ||
targetElement = triggerElement.value = null | ||
} | ||
}) | ||
|
||
useEventListener(document, 'pointerlockerror', () => { | ||
const currentElement = document!.pointerLockElement ?? element.value | ||
if (targetElement && currentElement === targetElement) { | ||
const action = document!.pointerLockElement ? 'release' : 'acquire' | ||
throw new Error(`Failed to ${action} pointer lock.`) | ||
} | ||
}) | ||
} | ||
|
||
async function lock(e: MaybeElementRef<MaybeHTMLElement> | Event, options?: PointerLockOptions) { | ||
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(options ?? pointerLockOptions) | ||
|
||
return await until(element).toBe(targetElement) | ||
} | ||
|
||
async function unlock() { | ||
if (!element.value) | ||
return false | ||
|
||
document!.exitPointerLock() | ||
|
||
await until(element).toBeNull() | ||
return true | ||
} | ||
|
||
return { | ||
isSupported, | ||
element, | ||
triggerElement, | ||
lock, | ||
unlock, | ||
} | ||
} | ||
|
||
export type UsePointerLockReturn = ReturnType<typeof usePointerLock> |