Skip to content

Commit

Permalink
fix(useFullscreen)!: better cross-platform compatibility (#2915)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
ferferga and antfu committed Apr 12, 2023
1 parent 0578145 commit 2e46781
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 98 deletions.
2 changes: 1 addition & 1 deletion packages/core/useFullscreen/index.md
Expand Up @@ -14,7 +14,7 @@ import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen()
```

Fullscreen specified element
Fullscreen specified element. Some platforms (like iOS's Safari) only allow fullscreen on video elements.

```ts
const el = ref<HTMLElement | null>(null)
Expand Down
187 changes: 90 additions & 97 deletions packages/core/useFullscreen/index.ts
@@ -1,6 +1,4 @@
/* this implementation is original ported from https://github.com/logaretm/vue-use-web by Abdelrahman Awad */

import { ref } from 'vue-demi'
import { computed, ref } from 'vue-demi'
import { tryOnScopeDispose } from '@vueuse/shared'
import type { MaybeElementRef } from '../unrefElement'
import { unrefElement } from '../unrefElement'
Expand All @@ -9,70 +7,6 @@ import type { ConfigurableDocument } from '../_configurable'
import { defaultDocument } from '../_configurable'
import { useSupported } from '../useSupported'

type FunctionMap = [
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
]

// from: https://github.com/sindresorhus/screenfull.js/blob/master/src/screenfull.js
const functionsMap: FunctionMap[] = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Safari iOS WebKit
[
'webkitEnterFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
] as any

export interface UseFullscreenOptions extends ConfigurableDocument {
/**
* Automatically exit fullscreen when component is unmounted
Expand All @@ -82,6 +16,14 @@ export interface UseFullscreenOptions extends ConfigurableDocument {
autoExit?: boolean
}

const eventHandlers = [
'fullscreenchange',
'webkitfullscreenchange',
'webkitendfullscreen',
'mozfullscreenchange',
'MSFullscreenChange',
] as any as 'fullscreenchange'[]

/**
* Reactive Fullscreen API.
*
Expand All @@ -93,35 +35,87 @@ export function useFullscreen(
target?: MaybeElementRef,
options: UseFullscreenOptions = {},
) {
const { document = defaultDocument, autoExit = false } = options
const targetRef = target || document?.querySelector('html')
const {
document = defaultDocument,
autoExit = false,
} = options

const targetRef = computed(() => unrefElement(target) ?? document?.querySelector('html'))
const isFullscreen = ref(false)
let map: FunctionMap = functionsMap[0]

const isSupported = useSupported(() => {
if (!document) {
return false
}
else {
const target = unrefElement(targetRef)
const requestMethod = computed<'requestFullscreen' | undefined>(() => {
return [
'requestFullscreen',
'webkitRequestFullscreen',
'webkitEnterFullscreen',
'webkitEnterFullScreen',
'webkitRequestFullScreen',
'mozRequestFullScreen',
'msRequestFullscreen',
].find(m => (document && m in document) || (targetRef.value && m in targetRef.value)) as any
})

const exitMethod = computed<'exitFullscreen' | undefined>(() => {
return [
'exitFullscreen',
'webkitExitFullscreen',
'webkitExitFullScreen',
'webkitCancelFullScreen',
'mozCancelFullScreen',
'msExitFullscreen',
].find(m => (document && m in document) || (targetRef.value && m in targetRef.value)) as any
})

const fullscreenEnabled = computed<'fullscreenEnabled' | undefined>(() => {
return [
'fullScreen',
'webkitIsFullScreen',
'webkitDisplayingFullscreen',
'mozFullScreen',
'msFullscreenElement',
].find(m => (document && m in document) || (targetRef.value && m in targetRef.value)) as any
})

for (const m of functionsMap) {
if (m[1] in document || (target && m[0] in target)) {
map = m
return true
const isSupported = useSupported(() =>
targetRef.value
&& document
&& requestMethod.value !== undefined
&& exitMethod.value !== undefined
&& fullscreenEnabled.value !== undefined,
)

const isElementFullScreen = (): boolean => {
if (fullscreenEnabled.value) {
if (document && document[fullscreenEnabled.value] != null) {
return document[fullscreenEnabled.value]
}
else {
const target = targetRef.value
// @ts-expect-error - Fallback for WebKit and iOS Safari browsers
if (target?.[fullscreenEnabled.value] != null) {
// @ts-expect-error - Fallback for WebKit and iOS Safari browsers
return Boolean(target[fullscreenEnabled.value])
}
}
}
return false
})

const [REQUEST, EXIT, ELEMENT,, EVENT] = map
}

async function exit() {
if (!isSupported.value)
return
if (document?.[ELEMENT])
await document[EXIT]()
if (exitMethod.value) {
if (document?.[exitMethod.value] != null) {
await document[exitMethod.value]()
}
else {
const target = targetRef.value
// @ts-expect-error - Fallback for Safari iOS
if (target?.[exitMethod.value] != null)
// @ts-expect-error - Fallback for Safari iOS
await target[exitMethod.value]()
}
}

isFullscreen.value = false
}
Expand All @@ -130,28 +124,27 @@ export function useFullscreen(
if (!isSupported.value)
return

await exit()
if (isElementFullScreen())
await exit()

const target = unrefElement(targetRef)
if (target) {
await target[REQUEST]()
const target = targetRef.value
if (requestMethod.value && target?.[requestMethod.value] != null) {
await target[requestMethod.value]()
isFullscreen.value = true
}
}

async function toggle() {
if (isFullscreen.value)
await exit()
else
await enter()
await (isFullscreen.value ? exit() : enter())
}

if (document) {
useEventListener(document, EVENT, () => {
isFullscreen.value = !!document?.[ELEMENT]
}, false)
const handlerCallback = () => {
isFullscreen.value = isElementFullScreen()
}

useEventListener(document, eventHandlers, handlerCallback, false)
useEventListener(() => unrefElement(targetRef), eventHandlers, handlerCallback, false)

if (autoExit)
tryOnScopeDispose(exit)

Expand Down

0 comments on commit 2e46781

Please sign in to comment.