Skip to content

Commit

Permalink
feat(usePointerLock): new function (#2590)
Browse files Browse the repository at this point in the history
* 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
4 people committed Jan 17, 2023
1 parent 691622a commit ae69fb8
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/components/index.ts
Expand Up @@ -37,6 +37,7 @@ export * from '../core/useOffsetPagination/component'
export * from '../core/useOnline/component'
export * from '../core/usePageLeave/component'
export * from '../core/usePointer/component'
export * from '../core/usePointerLock/component'
export * from '../core/usePreferredColorScheme/component'
export * from '../core/usePreferredContrast/component'
export * from '../core/usePreferredDark/component'
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -80,6 +80,7 @@ export * from './usePageLeave'
export * from './useParallax'
export * from './usePermission'
export * from './usePointer'
export * from './usePointerLock'
export * from './usePointerSwipe'
export * from './usePreferredColorScheme'
export * from './usePreferredContrast'
Expand Down
17 changes: 17 additions & 0 deletions packages/core/usePointerLock/component.ts
@@ -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))
}
},
})
63 changes: 63 additions & 0 deletions packages/core/usePointerLock/demo.vue
@@ -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>
26 changes: 26 additions & 0 deletions packages/core/usePointerLock/index.md
@@ -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>
```
95 changes: 95 additions & 0 deletions packages/core/usePointerLock/index.ts
@@ -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>

0 comments on commit ae69fb8

Please sign in to comment.