從零開始的野路子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 functionModalContent(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}; width: 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}; width: 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-width: 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,以便後來的模態框覆蓋先前的模態框:
點選“呼叫馬漢”,新的模態框會彈出:
此即最近對模態框的一些體會。
程式碼見: