关于浏览器全屏你不得不了解的知识与坑 您所在的位置:网站首页 通达信如何退出全屏 关于浏览器全屏你不得不了解的知识与坑

关于浏览器全屏你不得不了解的知识与坑

2023-04-24 17:51| 来源: 网络整理| 查看: 265

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

首先别骂标题党!这篇文章会从原生 dom api 开始,一路聊到 react 及相关组件库在全屏下的行为和应对措施,力求让你真正懂得如何和浏览器全屏打交道。如果你正打算实现一个全屏需求,那么本文绝对可以帮助你少往坑里踩。

废话少说,直接开始。

如何进入和退出全屏

如果现在项目经理跑过来告诉你,要加一个按钮,点一下就会把页面里某个表格全屏了,能做到吧。

简单!你想也没想就回答了。我直接 打开百度 大笔一挥,下面的实现就写好了,还附赠良好的兼容性:

/** * 全屏指定元素 */ function fullScreen(element) { const runfullScreen = element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullScreen || element.msRequestFullscreen; if (runfullScreen) runfullScreen.call(element); else { console.error('当前浏览器不支持部分全屏!'); } } /** * 退出全屏 */ function exitFullScreen() { const runExit = document.exitFullscreen || document.mozCancelFullScreen || document.webkitExitFullscreen || document.msExitFullscreen; if (runExit) runExit.call(document); else { console.error('当前浏览器不支持退出全屏!'); } } 复制代码

很好,没什么问题,写个小 demo 测试一下:

这里的内容会被全屏! 进入全屏 这里的不会被全屏 // 相关实现见上面 function fullScreen(element) { /** ... */ } function exitFullScreen() { /** ... */ } let isFullScreen = false; const button = document.getElementById('fullscreen-btn') button.addEventListener('click', e => { if (isFullScreen) { exitFullScreen(); button.innerText = '进入全屏'; } else { fullScreen(document.getElementById('fullscreen-container')); button.innerText = '退出全屏'; } isFullScreen = !isFullScreen; }); 复制代码

gif.gif

nice!按钮的提示也正常变更,div 也正确的被全屏,堪称完美。对于大多数人来说,对于浏览器全屏的了解就到此为止了(我也一样),但是本文不会到此结束,不知道你有没有发现,这个 demo 里还是有问题存在的:

按 Esc 的时候也会退出全屏,但是此时按钮没有被正确修改(依旧显示“退出全屏”)。

看来这个办法不行,应该通过其他更可靠的方法来修改按钮状态,比如监听对应的事件?

如何监听全屏事件

然后你又打开了搜索引擎,这时候查到的内容明显嘈杂了起来,有人说可以监听 esc 的 keydown 事件,有人说不好,应该监听 onresize 事件,然后通过 document.fullscreen 判断是否全屏了:

你想了想,感觉都不怎么靠谱。确实,你想的没错,这些网上抄来抄去的文章大都已经过时了,监听 keydown 可以,但是我们同时还要监听 F11 的 keydown(按 F11 也可以退出全屏),这样的解决办法会显得比较复杂且不可靠。

而 onresize 更是调整尺寸的时候也会触发,不加防抖的监听会带来无用的性能消耗。并且 document.fullscreen 属性更是已经被弃用了:

image.png

那么当下(2021/10/21)最好的写法是什么样的呢?答案是使用 fullscreenchange 事件和 document.fullscreenElement,如下:

let isFullScreen = false; const button = document.getElementById('fullscreen-btn'); const container = document.getElementById('fullscreen-container'); button.addEventListener('click', e => { if (isFullScreen) exitFullScreen(); else fullScreen(container); }); container.addEventListener('fullscreenchange', () => { isFullScreen = !!document.fullscreenElement; button.innerText = isFullScreen ? '退出全屏' : '进入全屏'; }); 复制代码

fullscreenchange 可以挂在任何 dom 元素上,而 document.fullscreenElement 则可以获取到当前正在全屏的元素(为 null 时说明没有全屏)。

到这里,我们已经了解了浏览器全屏的主要 api,那么我们干脆一鼓作气,看一下全部的全屏相关 api 都有那些吧(别紧张,并不多)。

浏览器全屏相关 API

我们可以在 这里 找到全屏相关的所有 api。首先就是两个主要 API,进入全屏和退出全屏:

image.png

这里需要提一嘴,很多人在不太了解的时候都会以为一共有四个 api(element 上的进入、退出全屏,document 上的进入、退出全屏 ),但是实际上它只有两个:调用指定 element 上的 requestFullscreen 让这个元素进入全屏 以及 调用全局的 document.exitFullscreen 来退出全屏。

然后是属性,只有两个 Document.fullscreenElement 和 document.fullscreenEnabled,第一个咱们刚才已经用过了,全局只会有一个被全屏的 dom 元素,可以通过 fullscreenElement 拿到它。而 fullscreenEnabled 则可以用来判断当前是否支持全屏,它还有一个如下的兼容写法:

const isFullScreen = () => { return document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen || document.msFullscreenEnabled; } 复制代码

注意这两者的区别,fullscreenElement 全屏时有值,不全屏时没值,而 fullscreenEnabled 则只要当前支持全屏就会一直为 true。

最后就是事件啦,也是只有两个 fullscreenchange 和 fullscreenerror。顾名思义,全屏状态切换时触发,全屏失败时触发,我们刚才也用到了,没什么好说的。

在 react 中使用全屏

这里顺便贴一下上面的使用方法在 react hook 里的实现,使用了 antd 作为组件库,直接复制就能用:

import React, { useState, useEffect } from 'react'; import { Button } from 'antd'; import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons'; import PropTypes from 'prop-types'; /** * 全屏指定元素 */ function fullScreen(element) { const runfullScreen = element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullScreen || element.msRequestFullscreen; if (runfullScreen) runfullScreen.call(element); else { console.error('当前浏览器不支持部分全屏!'); } } /** * 退出全屏 */ function exitFullScreen() { const runExit = document.exitFullscreen || document.mozCancelFullScreen || document.webkitExitFullscreen || document.msExitFullscreen; if (runExit) runExit.call(document); else { console.error('当前浏览器不支持退出全屏!'); } } /** * 全屏按钮组件 */ const FullScreenBtn = function (props) { const { targetRef, onChange } = props; const [isFullScren, setIsFullScren] = useState(false); // 监听全屏变更事件并修改组件状态 useEffect(() => { const listener = e => { const isFullScreen = !!document.fullscreenElement; // console.log(isFullScreen ? '启动全屏!' : '退出全屏!'); setIsFullScren(isFullScreen); onChange && onChange(isFullScreen); } targetRef.current.addEventListener('fullscreenchange', listener); return () => targetRef.current.removeEventListener('fullscreenchange', listener); }, [targetRef.current]); /** * 回调 - 切换全屏 */ const onClick = () => { if (!targetRef.current) return console.error('未找到要全屏的元素 targetRef'); if (isFullScren) exitFullScreen(); else fullScreen(targetRef.current); } return ( {isFullScren ? 退出全屏 : 进入全屏 } ) } FullScreenBtn.propTypes = { // 要进行全屏的 dom ref targetRef: PropTypes.object.isRequired, // 全屏状态变化时触发 onChange: PropTypes.func }; export default FullScreenBtn; 复制代码

用法也很简单,这个文件导出了一个 FullScreenBtn 组件,把要全屏的元素 ref 传递给 targetRef 属性即可。也可以使用 onChange 来监听全屏状态变更事件。

OK,聊完了用法,现在我们来看一下全屏时会遇到的一些问题:

TypeError: fullscreen error

在有些时候调用 element.requestFullscreen() 会出现这个错误:

Promise {: TypeError: fullscreen error at :1:4} Uncaught (in promise) TypeError: fullscreen error at :1:4 复制代码

其实我们可以提前使用刚才提到的 fullscreenEnabled 来进行预判:

element.ownerDocument.fullscreenEnabled // => 返回 false 则调用全屏一定会报错。 复制代码

导致这个问题的原因时什么呢?很简单,这个元素处在一个 iframe 里,而这个 iframe 不允许全屏。其实也有 其他原因 可能会导致这个报错,但是我们基本遇不到。

解决办法也很简单,找到对应的 iframe,在其 sandbox 属性里添加 allow-fullscreen 即可:

... 复制代码

关于 iframe 的 sandbox 属性可以看 iframe: The Inline Frame element - HTML: HyperText Markup Language | MDN (mozilla.org)。

为什么有时候进入全屏了按 Ecs 会没用?

相信很多人都遇到过这个问题,全屏之后按 Ecs 发现怎么都退不出来,只能按 F11 或者鼠标挪到最上面点 ×。而导致这个问题的原因说起来也挺悲哀:浏览器中存在两套全屏规则!

第一套就是我们刚才讲的,通过 web API 进入的全屏。此时 可以通过 Esc 和 F11 退出全屏,也可以通过 api 正常监听和退出全屏。 第二套则是浏览器级别的全屏,通过 F11(或右上角设置里的全屏按钮)进入的全屏。此时 只能通过 F11 退出全屏。

这两者在进入全屏时给出的浏览器提示也是不一样的:

image.png

image.png

那么悲哀在那里呢?悲哀在 第二种方式进入的全屏是无法通过 api 访问到的!

如果你不信的话,可以尝试下,现在按 F11 打开全屏后在控制台输入以下代码,你会发现即使是浏览器已经全屏了,api 也完全不知道:

// 获取当前全屏元素 document.fullscreenElement // 尝试退出全屏 document.exitFullscreen() 复制代码

image.png

针对第二种全屏方式的监听一直是很多人讨论的问题,例如老办法,监听 F11 的 keydown 事件(但是无法处理点击浏览器右上角全屏按钮 ),或者在 onresize 里监听 window.outterHeight 和 window.innerHeight 的差异(某些浏览器里全屏后者两者并不是完全相同,存在兼容性问题 )。

这个问题目前我没找到合适的解决办法,如果有人有好点子的话欢迎评论分享。

全屏后都黑了?聊聊与全屏相关的 CSS

在实际开发的时候,有时候我们会遇到一些比较让人崩溃的样式问题,例如 element 官网上一个很正常的表单:

image.png

使用 requestFullscreen 将其全屏之后:

image.png

nooooooooooooooo!表单你怎么了!看到这一幕相信很多人心嘎吱一下就凉了半截,完了这么大的样式问题,不好处理。其实不用担心,这只是默认的用户代理样式表在作怪,我们打开控制台选中一下被全屏的 dom,在样式最下面就能看到罪魁祸首:

image.png

这里简单介绍一下::fullscreen 伪类代表了被全屏后的元素本身,而 ::backdrop 伪元素代表了全屏之后的背景画面(比如一个元素全屏之后没办法铺满整个窗口 )。了解了原因之后就很好解决了,毕竟用户代理样式表的优先级是相当低的,我们稍微写一下就能覆盖它们:

// 在 css 里 #fullscreen-container::backdrop { background: #fff; } // 在 less 里 #fullscreen-container { &::backdrop { background: #fff; } } 复制代码

而 :fullscreen 则可以用来解决一些更实际的问题,例如:

被全屏元素没有指定内边框导致直接贴住了窗口边缘。 被全屏元素内容超出屏幕但是却滚动不了。

都可以用下面的样式来解决:

// 在 css 里 #fullscreen-container:fullscreen { padding: 32px; overflow-y: auto; } // 在 less 里 #fullscreen-container { &:fullscreen { padding: 32px; overflow-y: auto; } } 复制代码

关于这两者的详细介绍见:::backdrop - CSS(层叠样式表) | MDN (mozilla.org) 和 :fullscreen - CSS(层叠样式表) | MDN (mozilla.org)。

弹出表单项被遮挡问题

这次我们换 antd 官网来霍霍,打开 表单 Form - Ant Design,然后 F12 把这个元素全屏,然后就会发现,嗯弹出表单怎么点不动了?

gif.gif

造成这个问题的原因是这些组件库实现弹出框的做法一般都是在 body 下创建对应的 dom 节点,而我们全屏了某个 body 下的 dom 元素后,这些弹出框的 dom 节点就被我们的全屏元素盖住了,自然就看不到了,那么怎么解决呢?

首先是比较优雅的方案,绝大多数主流组件库的弹出式组件都会给你提供这么一个方法。element 里则是 popper-append-to-body 属性:

image.png

我们就可以用它来解决这个问题,例如我们使用了上文里给的全屏按钮,只需要把要全屏的 dom ref 扔进去就好了:

import React, { useRef } from 'react'; import { DatePicker } from 'antd'; import FullScreenBtn from '@/components/FullScreenBtn'; export default function Comp(props) { const fullScreenRef = useRef(undefined); return ( {/** 注意这里提供了要全屏的元素,由此来让弹出框可以校准到正确的位置 */} fullScreenRef.current}/> ) } 复制代码

但是如果我们用不了这个办法,比如组件库没有提供这个属性,又或者要全屏的 dom 元素里有好多要弹出的组件。那么该怎么办呢?没有问题,这里再给一个不太优雅但是同样有效的方案,全屏整个 Document,然后修改要展示的节点样式,让其覆盖住整个窗口:

export default function Comp(props) { // 这里别忘了改!!!!!!!!!直接全屏整个 dom const fullScreenRef = useRef(document.documentElement); const [isFullScreen, setIsFullScreen] = useState(false); // 如果全屏了就让他覆盖整个窗口 const contianerStyle = isFullScreen ? { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, background: '#fff' } : {}; return ( ) } 复制代码

注意这种方法一定要检查会不会引发一些样式问题。

写在最后

本文介绍了如何优雅的使用现代全屏 API。以及对在开发全屏需求时可能会遇到的一些问题和坑,希望对大家有所帮助,觉得可以的话不要吝啬点赞哦。

如果看完之后你觉得不够尽兴的话,那么推荐读一下 Guide to the Fullscreen API - Web APIs | MDN (mozilla.org),很多时候我们会轻视 MDN,但是圣经永远是圣经。

参考 ::backdrop - CSS(层叠样式表) | MDN (mozilla.org) :fullscreen - CSS(层叠样式表) | MDN (mozilla.org) Document: fullscreenchange event - Web APIs | MDN (mozilla.org) Fullscreen API Standard (whatwg.org) Document.exitFullscreen()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有