小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
首先别骂标题党!这篇文章会从原生 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 测试一下:
<body>
<div id="fullscreen-container" style="background-color: #fff;">
这里的内容会被全屏!
<button id="fullscreen-btn">进入全屏</button>
</div>
<div>
这里的不会被全屏
</div>
</body>
<script>
// 相关实现见上面
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;
});
</script>
nice!按钮的提示也正常变更,div 也正确的被全屏,堪称完美。对于大多数人来说,对于浏览器全屏的了解就到此为止了(我也一样),但是本文不会到此结束,不知道你有没有发现,这个 demo 里还是有问题存在的:
按 Esc 的时候也会退出全屏,但是此时按钮没有被正确修改(依旧显示“退出全屏”)。
看来这个办法不行,应该通过其他更可靠的方法来修改按钮状态,比如监听对应的事件?
如何监听全屏事件
然后你又打开了搜索引擎,这时候查到的内容明显嘈杂了起来,有人说可以监听 esc
的 keydown
事件,有人说不好,应该监听 onresize
事件,然后通过 document.fullscreen
判断是否全屏了:
你想了想,感觉都不怎么靠谱。确实,你想的没错,这些网上抄来抄去的文章大都已经过时了,监听 keydown 可以,但是我们同时还要监听 F11 的 keydown(按 F11 也可以退出全屏),这样的解决办法会显得比较复杂且不可靠。
而 onresize 更是调整尺寸的时候也会触发,不加防抖的监听会带来无用的性能消耗。并且 document.fullscreen
属性更是已经被弃用了:
那么当下(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,进入全屏和退出全屏:
这里需要提一嘴,很多人在不太了解的时候都会以为一共有四个 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 (
<Button onClick={onClick}>
{isFullScren ?
<span><FullscreenExitOutlined /> 退出全屏</span> :
<span><FullscreenOutlined /> 进入全屏</span>
}
</Button>
)
}
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 {<rejected>: TypeError: fullscreen error
at <anonymous>:1:4}
Uncaught (in promise) TypeError: fullscreen error
at <anonymous>:1:4
其实我们可以提前使用刚才提到的 fullscreenEnabled
来进行预判:
element.ownerDocument.fullscreenEnabled // => 返回 false 则调用全屏一定会报错。
导致这个问题的原因时什么呢?很简单,这个元素处在一个 iframe 里,而这个 iframe 不允许全屏。其实也有 其他原因 可能会导致这个报错,但是我们基本遇不到。
解决办法也很简单,找到对应的 iframe,在其 sandbox
属性里添加 allow-fullscreen
即可:
<iframe sandbox="allow-fullscreen allow-forms allow-modals allow-popups ...">
...
</iframe>
关于 iframe 的 sandbox 属性可以看 iframe: The Inline Frame element - HTML: HyperText Markup Language | MDN (mozilla.org)。
为什么有时候进入全屏了按 Ecs 会没用?
相信很多人都遇到过这个问题,全屏之后按 Ecs 发现怎么都退不出来,只能按 F11 或者鼠标挪到最上面点 ×。而导致这个问题的原因说起来也挺悲哀:浏览器中存在两套全屏规则!
- 第一套就是我们刚才讲的,通过 web API 进入的全屏。此时 可以通过 Esc 和 F11 退出全屏,也可以通过 api 正常监听和退出全屏。
- 第二套则是浏览器级别的全屏,通过 F11(或右上角设置里的全屏按钮)进入的全屏。此时 只能通过 F11 退出全屏。
这两者在进入全屏时给出的浏览器提示也是不一样的:
那么悲哀在那里呢?悲哀在 第二种方式进入的全屏是无法通过 api 访问到的!
如果你不信的话,可以尝试下,现在按 F11 打开全屏后在控制台输入以下代码,你会发现即使是浏览器已经全屏了,api 也完全不知道:
// 获取当前全屏元素
document.fullscreenElement
// 尝试退出全屏
document.exitFullscreen()
针对第二种全屏方式的监听一直是很多人讨论的问题,例如老办法,监听 F11
的 keydown 事件(但是无法处理点击浏览器右上角全屏按钮 ),或者在 onresize
里监听 window.outterHeight
和 window.innerHeight
的差异(某些浏览器里全屏后者两者并不是完全相同,存在兼容性问题 )。
这个问题目前我没找到合适的解决办法,如果有人有好点子的话欢迎评论分享。
全屏后都黑了?聊聊与全屏相关的 CSS
在实际开发的时候,有时候我们会遇到一些比较让人崩溃的样式问题,例如 element 官网上一个很正常的表单:
使用 requestFullscreen
将其全屏之后:
nooooooooooooooo!表单你怎么了!看到这一幕相信很多人心嘎吱一下就凉了半截,完了这么大的样式问题,不好处理。其实不用担心,这只是默认的用户代理样式表在作怪,我们打开控制台选中一下被全屏的 dom,在样式最下面就能看到罪魁祸首:
这里简单介绍一下::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 把这个元素全屏,然后就会发现,嗯弹出表单怎么点不动了?
造成这个问题的原因是这些组件库实现弹出框的做法一般都是在 body 下创建对应的 dom 节点,而我们全屏了某个 body 下的 dom 元素后,这些弹出框的 dom 节点就被我们的全屏元素盖住了,自然就看不到了,那么怎么解决呢?
首先是比较优雅的方案,绝大多数主流组件库的弹出式组件都会给你提供这么一个方法。element 里则是 popper-append-to-body
属性:
我们就可以用它来解决这个问题,例如我们使用了上文里给的全屏按钮,只需要把要全屏的 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 (
<div ref={fullScreenRef}>
<FullScreenBtn targetRef={fullScreenRef} />
{/** 注意这里提供了要全屏的元素,由此来让弹出框可以校准到正确的位置 */}
<DatePicker getPopupContainer={() => fullScreenRef.current}/>
</div>
)
}
但是如果我们用不了这个办法,比如组件库没有提供这个属性,又或者要全屏的 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 (
<div style={contianerStyle}>
<FullScreenBtn targetRef={fullScreenRef} onChange={setIsFullScreen} />
<DatePicker />
</div>
)
}
注意这种方法一定要检查会不会引发一些样式问题。
写在最后
本文介绍了如何优雅的使用现代全屏 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()
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!