Typescript封装全屏API

421 阅读3分钟

最近的开发工作中遇到了一个需求是在pc或者pad上可以像ppt一样全屏显示当前页面的内容。在这里记录一下开发过程中遇到的问题。

web 提供的全屏API

Document.fullscreenEnabled // 是否支持全屏模式
Element.requestFullscreen() // 发出异步请求进入全屏模式
Document.exitFullscreen() // 退出全屏
Document.onfullscreenchange() // 进入或退出全屏时触发的事件
Document.onfullscreenerror() // 当浏览器无法进入全屏模式时会触发该事件
Document.fullscreenElement() // 返回当前的全屏元素或者当前没有全屏元素返回null
  • 全屏模式,只能由用户行为触发。比如无法一进入页面就由JS直接调起全屏,此时会有错误提示。
  • 用户可以选择按ESC(或F11) 键退出全屏模式。此行为为系统级别的事件,无法在浏览器中捕捉或监听该操作。

class 封装全屏API

interface htmlElement extends Document {
  // 是否支持全屏
  webkitFullscreenEnabled: boolean;
  mozFullScreenEnabled: boolean;
  msFullscreenEnabled: boolean;
  // 是否具有全屏元素
  msFullscreenElement: Element;
  mozFullScreenElement: Element;
  webkitFullscreenElement: Element;
}

class FullScreen {
  private _prefixName = ''; // 各浏览器前缀
  private _isSupportFullscreen = true; // 浏览器是否支持
  private _dom: HTMLDivElement | null = null;

  constructor() {
    this._getPrefix();
  }

  get isSupportFullscreen(): boolean {
    return this._isSupportFullscreen;
  }

  private _getPrefix(): void {
    let fullscreenEnabled;
    const doc = document as htmlElement;
    // 判断浏览器前缀
    if (doc.fullscreenEnabled) {
      fullscreenEnabled = doc.fullscreenEnabled;
    } else if (doc.webkitFullscreenEnabled) {
      fullscreenEnabled = doc.webkitFullscreenEnabled;
      this._prefixName = 'webkit';
    } else if (doc.mozFullScreenEnabled) {
      fullscreenEnabled = doc.mozFullScreenEnabled;
      this._prefixName = 'moz';
    } else if (doc.msFullscreenEnabled) {
      fullscreenEnabled = doc.msFullscreenEnabled;
      this._prefixName = 'ms';
    }
    if (!fullscreenEnabled) {
      this._isSupportFullscreen = false;
      console.log('This browser does not support full screen!');
    }
  }

  /**
   * @description: 将传进来的元素全屏
   * @param {String} domName 要全屏的dom名称
   */
  public requestFullscreen(dom: string): void {
    if (!this._isSupportFullscreen || !this._dom) {
      return;
    }
    const methodName = (
      this._prefixName === '' ? 'requestFullscreen' : `${this._prefixName}RequestFullScreen`
    ) as 'requestFullscreen';
    this._dom?.[methodName]().catch((err) => {
      alert(`An error occurred while trying to switch into fullscreen mode: ${err.message} (${err.name})`);
    });
  }

  /**
   * @description: 退出全屏
   */
  public exitFullscreen(): void {
    if (!this._isSupportFullscreen || !this.isElementFullScreen()) {
      return;
    }
    const methodName = (
      this._prefixName === '' ? 'exitFullscreen' : `${this._prefixName}ExitFullscreen`
    ) as 'exitFullscreen';
    document[methodName]();
  }
  /**
   * @description: 监听进入/离开全屏
   * @param {Function} enter 进入全屏的回调
   * @param {Function} quit 离开全屏的回调
   */
  public screenChange(enter: (e: Event) => void, quit: (e: Event) => void): void {
    if (!this._isSupportFullscreen) return;
    const methodName = `on${this._prefixName}fullscreenchange` as 'onfullscreenerror';
    document[methodName] = (e: Event): void => {
      if (this.isElementFullScreen()) {
        this._dom?.classList.add('fullscreen');
        enter && enter(e); // 进入全屏回调
      } else {
        quit && quit(e); // 离开全屏的回调
        this._dom?.classList.remove('fullscreen');
      }
    };
  }
  /**
   * @description: 浏览器无法进入全屏时触发,可能是技术原因,也可能是用户拒绝:比如全屏请求不是在事件处理函数中调用,会在这里拦截到错误
   * @param {Function} enterErrorFn 回调
   */
  public screenError(enterErrorFn: (e: Event) => void): void {
    if (!this._isSupportFullscreen) return;
    const methodName = `on${this._prefixName}fullscreenerror` as 'onfullscreenerror';
    document[methodName] = (e: Event): void => {
      enterErrorFn && enterErrorFn(e);
    };
  }
  /**
   * @description: 检测有没有元素处于全屏状态
   * @return 布尔值
   */
  public isElementFullScreen(): boolean {
    const doc = document as htmlElement;
    const fullscreenElement =
      doc.fullscreenElement || doc.msFullscreenElement || doc.mozFullScreenElement || doc.webkitFullscreenElement;
    return !!fullscreenElement;
  }
  /**
   * @description: 打开全屏
   * @param {string} 全屏元素
   */
  public open(dom: string, enter?: (e: Event) => void, quit?: (e: Event) => void): void {
    if (!dom || !document.querySelector(dom)) {
      return;
    }
    this._dom = document.querySelector(dom);
    this.requestFullscreen(dom);
    this.screenChange(enter as (e: Event) => void, quit as (e: Event) => void);
    this.screenError(() => {
      console.log('Failed to enter full screen!');
    });
  }
}
export default new FullScreen();

全屏兼容问题

在这次的开发中主要是在PC或pad上全屏,所以兼容性也只是针对这两种设备。

1. 部分ipad在 chrome 浏览器下不支持全屏

developer.mozilla.org/en-US/docs/…

1714114245.png

2. :fullscreen 兼容性不好,不建议使用

developer.mozilla.org/en-US/docs/…

1715653766.png 有另外的解决方案:在进入或退出全屏时添加class进行全屏样式的设置

// Fullscreen.screenChange
document[methodName] = (e: Event): void => {
  if (this.isElementFullScreen()) {
    this._dom?.classList.add('fullscreen');
    enter && enter(e); // 进入全屏回调
  } else {
    quit && quit(e); // 离开全屏的回调
    this._dom?.classList.remove('fullscreen');
  }
};

3. esc退出全屏

当设备为pad时,没有esc按键,无法按esc键退出全屏,但是部分设备可以使用向下滑动退出全屏。
有些设备不支持向下滑动退出全屏,比如三星pad在三星浏览器。
所以建议在pad平台上添加显式按钮退出全屏。

  private _renderCloseBtn = (): void => {
    // 是否是pad
    if (isPad()) {
      const btn = document.createElement('button');
      btn.textContent = 'Exit';
      this._closeBtn = document.createElement('div');
      this._closeBtn.appendChild(btn);
      this._closeBtn.classList.add('fullscreen-close');
      this._dom?.appendChild(this._closeBtn);
    }
  };