最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 关于浏览器全屏你不得不了解的知识与坑 - 掘金

    正文概述 掘金(hopgoldy)   2021-10-21   1532

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

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

    首先别骂标题党!这篇文章会从原生 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 的时候也会退出全屏,但是此时按钮没有被正确修改(依旧显示“退出全屏”)。

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

    如何监听全屏事件

    然后你又打开了搜索引擎,这时候查到的内容明显嘈杂了起来,有人说可以监听 esckeydown 事件,有人说不好,应该监听 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.outterHeightwindow.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介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元