zoukankan      html  css  js  c++  java
  • 从零开始的野路子React/Node(6)关于模态框的二三事

    前一阵遇到过一个需求,要求在App中点击某个按钮会弹出一个对话框(即模态框Modal)。第一件事自然是看看公司内部的组件库有没有已经实现的功能,结果这一看把我看得云里雾里的,这是神马?这又是神马?算了,还是自己写(抄)一个吧。

    在网上翻找了许久,始终没有特别满意的实现,直到我找到了这篇:

    https://blog.bitsrc.io/build-a-full-featured-modal-dialog-form-with-react-651dcef6c571

    实现很简洁,却又非常好用。稍加改动,啊,真香~

    这个模态框一共由3部分组成:

    其中ModalContent负责实现模态框内部的内容,你现在框里显示信息也好,表单也好,加几个按钮,都在这里体现;

    TriggerButton则负责在父页面上实现一个按钮,用来触发模态框的弹出,点了它就会弹出模态框;

    ModalContainer就是个容器,负责将ModalContent和TriggerButton融合起来,以及模态框显示/隐藏的一些逻辑。

    1、ModalContent

    import React from 'react';
    import ReactDOM from 'react-dom';
    import FocusTrap from 'focus-trap-react';
    import styled from 'styled-components';
    
    export default function ModalContent(props) {
        const {
            modalRef,
            buttonRef,
            onKeyDown,
            closeModal,
            zindex
        } = props;
    
        return ReactDOM.createPortal(
            <FocusTrap>
                <aside 
                    tag="aside"
                    role="dialog"
                    tabIndex="-1"
                    aria-modal="true"
                    onKeyDown={onKeyDown}>
                    <StyledOverlay zindex={zindex}/>
                    <StyledWrapper zindex={zindex}>
                        <StyledModal ref={modalRef}>
                            <div>
                                { props.children }
                                <button 
                                    ref={buttonRef} 
                                    aria-label="Close Modal"
                                    aria-labelledby="close-modal"
                                    onClick={closeModal}>退下吧</button>  
                            </div>
                        </StyledModal>
                    </StyledWrapper>
                </aside>
            </FocusTrap>, document.body
        );
    };
    
    const StyledOverlay = styled.div`
        position: fixed;
        top: 0;
        left: 0;
        z-index: ${props => props.zindex + 1000};
         100vw;
        height: 100vh;
        background-color: #000;
        opacity: 0.5;
    ` //用于Modal弹出后遮蔽其他原内容
    
    const StyledWrapper = styled.div`
        position: fixed;
        top: 0;
        left: 0;
        z-index: ${props => props.zindex + 1010};
         100%;
        height: 100%;
        overflow-x: hidden;
        overflow-y: auto;
        outline: 0;
    `
    
    const StyledModal = styled.div`
        z-index: 100;
        background: white;
        position: relative;
        top: 80px;
        margin: 1.75rem auto;
        border-radius: 3px;
        max- 1000px;
        padding: 2rem;
    `

    看上去还挺复杂的,其实真正模态框里的内容只有<div>和</div>之间的部分,其他的部分可以看做是框体和框外的实现。

    其中createPortal负责创建模态框。FocusTrap负责把Tab限制在框内部的元素上,在FocusTrap存在的情况下,你随便怎么按Tab键,高亮都只会在模态框内部的元素间跳来跳去。如果没有FocusTrap的话,可能你按几下Tab,高亮就跳到模态框背后的内容上了。

    此外,StyledOverlay负责一个遮罩效果,遮住模态框背后的内容,它的z-index一定要高于父页面;

    StyledWrapper类似于一个模态框的外部容器,它的z-index一定要高于StyledOverlay;

    StyledModal则是负责模态框的本体长什么样。

    <div>和</div>之间的内容包含了两部分,一部分是{ props.children },这样一来我们可以接受任意子组件作为模态框中的内容,更加灵活。另一部分是个button,用来关闭模态框。

    另外,这里用styled-components来替代了css,而且props中的属性可以传入其中,我们用这一方法来控制z-index,从而方便我们之后“框中框”中的使用。

    2、TriggerButton

    import React from 'react';
    
    export default function TriggerButton(props) {
        const {
            triggerText, 
            buttonRef, 
            showModal
        } = props
    
        return (
            <button 
                ref={buttonRef} 
                onClick={showModal}>{ triggerText }</button>
        );
    };

    这里的内容很简单,主要就是点击时调用父组件的showModal函数从而打开模态框。

    3、ModalContainer

    这一部分是相对而言最复杂的(试图转成函数式组件,但貌似没法使用ref,放弃……):

    import React, { Component } from 'react';
    import ModalContent from "./ModalContent";
    import TriggerButton from "./TriggerButton";
    
    export default class ModalContainer extends Component {
        state = {isShown: false};
        
        showModal = () => {
            this.setState({isShown: true});
            this.toggleScrollLock();
        };
    
        closeModal = () => {
            this.setState({isShown: false});
            this.toggleScrollLock();
        };
    
        onKeyDown = (event) => {
            if (event.keyCode === 27) {
                this.closeModal();
            }; //按下ESC
        };
    
        toggleScrollLock = () => {
            document.querySelector("html").classList.toggle("scroll-lock");
        };
    
        render () {
            return (
                <React.Fragment>
                    <TriggerButton
                        showModal={this.showModal}
                        buttonRef={(n) => {this.TriggerButton=n}}
                        triggerText={this.props.buttonText}/>
                    {this.state.isShown ? 
                    <ModalContent
                        title={this.props.title}
                        modalRef={(n) => {this.modal=n}}
                        buttonRef={(n) => {this.closeButton=n}}
                        closeModal={this.closeModal}
                        onKeyDonw={this.onKeyDown}
                        zindex={this.props.zindex || 0}
                        children={this.props.children}/> : 
                    null}  
                </React.Fragment>
            );
        }
    };

    isShown负责记录模态框处于显示还是隐藏的状态;

    showModal和closeModal分别负责打开和关闭模态框;

    onKeyDown负责在按下esc的时候关闭模态框;

    toggleScrollLock用于锁定/解锁滚动(作用我没明白……试过去掉,好像没什么影响)。

    最后的部分就是一个TriggerButton和一个条件渲染的ModalContent,isShown为true的情况下显示ModalContent,否则隐藏之,从而实现打开/关闭模态框的效果。

    ModalContent的zindex在未指定的情况下为0,对应正常的模态框,如果我们传入一个值,还可以实现“框中框的效果”。

    4、齐活了

    现在我们来试试不同的模态框组合效果,把容器组件加入App中即可:

    第1个框(张龙)没有任何子组件,因此只有一个关闭按钮:

    第2个框(赵虎)有一个子组件,因此框中会显示该子组件以及关闭按钮:

    第3个框(王朝)包含了一个标题组件以及另一个模态框作为子组件,此处我们设置了zindex,以便后来的模态框覆盖先前的模态框:

    点击“呼叫马汉”,新的模态框会弹出:

    此即最近对模态框的一些体会。

    代码见:

     https://github.com/SilenceGTX/play_with_modal

  • 相关阅读:
    抓取网页萃取网页内容的代码 选择自 liujien 的 Blog
    asp.net2.0
    C# veriosn 3
    ASP.NET常用代码
    vbs automation copy file X: to X
    教学进度
    八岁女孩打电话给爆破公司要求炸毁学校(带中文翻译)
    闲话排序问题
    奋进号(Endeavour)太空梭,将执行最后一次太空任务
    Chrome: Google Translate 开始支持语音输入了!
  • 原文地址:https://www.cnblogs.com/silence-gtx/p/13769865.html
Copyright © 2011-2022 走看看