Skip to content

useFullscreen

作用

管理 DOM 全屏的 Hook。

原理

通过 screenfull 这个包来实现的。

源码

ts
import { useEffect, useState, useRef } from 'react'
import screenfull from 'screenfull'
import useLatest from '../useLatest'
import useMemoizedFn from '../useMemoizedFn'
import type { BasicTarget } from '../utils/domTarget'
import { getTargetElement } from '../utils/domTarget'
import { isBoolean } from '../utils'

export interface PageFullscreenOptions {
  className?: string
  zIndex?: number
}

export interface Options {
  onExit?: () => void
  onEnter?: () => void
  pageFullscreen?: boolean | PageFullscreenOptions
}

const useFullscreen = (target: BasicTarget, options?: Options) => {
  const { onExit, onEnter, pageFullscreen = false } = options || {}

  // pageFullscreen 为 true 时,className 默认为 'ahooks-page-fullscreen',zIndex 默认为 999999
  const { className = 'ahooks-page-fullscreen', zIndex = 999999 } =
    isBoolean(pageFullscreen) || !pageFullscreen ? {} : pageFullscreen

  // 保存 onExit 和 onEnter 的最新值
  const onExitRef = useLatest(onExit)
  const onEnterRef = useLatest(onEnter)

  // The state of full screen may be changed by other scripts/components,
  // so the initial value needs to be computed dynamically.
  const [state, setState] = useState(getIsFullscreen)

  // ref 保存是否全屏的状态值
  const stateRef = useRef(getIsFullscreen())

  // 判断是否全屏
  function getIsFullscreen() {
    return (
      screenfull.isEnabled &&
      !!screenfull.element &&
      screenfull.element === getTargetElement(target)
    )
  }

  // 根据是否全屏,调用 onExit 或 onEnter
  const invokeCallback = (fullscreen: boolean) => {
    if (fullscreen) {
      onEnterRef.current?.()
    } else {
      onExitRef.current?.()
    }
  }

  // 更新是否全屏的状态值
  const updateFullscreenState = (fullscreen: boolean) => {
    // Prevent repeated calls when the state is not changed.
    if (stateRef.current !== fullscreen) {
      invokeCallback(fullscreen)
      setState(fullscreen)
      stateRef.current = fullscreen
    }
  }

  // 监听全屏状态的变化,更新是否全屏的状态值
  const onScreenfullChange = () => {
    const fullscreen = getIsFullscreen()

    updateFullscreenState(fullscreen)
  }

  // 切换页面全屏
  const togglePageFullscreen = (fullscreen: boolean) => {
    const el = getTargetElement(target)
    if (!el) {
      return
    }

    let styleElem = document.getElementById(className)

    if (fullscreen) {
      el.classList.add(className)

      if (!styleElem) {
        styleElem = document.createElement('style')
        styleElem.setAttribute('id', className)
        styleElem.textContent = `
          .${className} {
            position: fixed; left: 0; top: 0; right: 0; bottom: 0;
            width: 100% !important; height: 100% !important;
            z-index: ${zIndex};
          }`
        el.appendChild(styleElem)
      }
    } else {
      el.classList.remove(className)

      if (styleElem) {
        styleElem.remove()
      }
    }

    updateFullscreenState(fullscreen)
  }

  // 进入全屏
  const enterFullscreen = () => {
    const el = getTargetElement(target)
    if (!el) {
      return
    }

    if (pageFullscreen) {
      togglePageFullscreen(true)
      return
    }
    if (screenfull.isEnabled) {
      try {
        screenfull.request(el)
      } catch (error) {
        console.error(error)
      }
    }
  }

  // 退出全屏
  const exitFullscreen = () => {
    const el = getTargetElement(target)
    if (!el) {
      return
    }

    if (pageFullscreen) {
      togglePageFullscreen(false)
      return
    }
    if (screenfull.isEnabled && screenfull.element === el) {
      screenfull.exit()
    }
  }

  // 切换全屏
  const toggleFullscreen = () => {
    if (state) {
      exitFullscreen()
    } else {
      enterFullscreen()
    }
  }

  useEffect(() => {
    // 如果不支持全屏,或者 pageFullscreen 为 true,直接返回
    if (!screenfull.isEnabled || pageFullscreen) {
      return
    }

    // 监听全屏状态的变化,更新是否全屏的状态值
    screenfull.on('change', onScreenfullChange)

    // 组件卸载时,移除监听
    return () => {
      screenfull.off('change', onScreenfullChange)
    }
  }, [])

  return [
    state,
    {
      enterFullscreen: useMemoizedFn(enterFullscreen),
      exitFullscreen: useMemoizedFn(exitFullscreen),
      toggleFullscreen: useMemoizedFn(toggleFullscreen),
      isEnabled: screenfull.isEnabled,
    },
  ] as const
}

export default useFullscreen

如有转载或 CV 的请标注本站原文地址