亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

詳解使用React制作一個模態框

2024-05-06 16:49:04
字體:
來源:轉載
供稿:網友

模態框是一個常見的組件,下面讓我們使用 React 實現一個現代化的模態框吧。

組件設計

模態框想必大家都很熟悉,是工作中常用的組件,可以讓我們填寫或展示一些信息而不必打開一個新頁面。在開始編碼之前,我們先來了解一個 React 模態框組件應該如何設計。

React 是一個狀態(數據)驅動的前端框架,一個模態框最重要的狀態就是打開和關閉,visible,當 visible 為 true 時,模態框打開,反之亦然。

由于 React 所提倡的是一種聲明式,組件化的開發體驗,每個組件都是 狀態 => 界面 的映射,所以,我們把 visible 做為模態框組件的一個 prop,通過傳入 prop 來控制

模態框的顯示和隱藏,同時該組件還接受一個 onClose 的 prop,用來關閉模態框。

<Modal visible={modalVisble} onClose={this.onModalClose} />

一個完整的模態框還需要標題和內容,因此,我們還需要一個 header 的 prop 來傳遞模態框的 header,并把 Modal 組件的 children 作為模態框的內容 content。最后,我們的模態框 Modal 的調用方式是這樣的:

import React, { useEffect, useState } from 'react';import Modal from './components/modal';function App() { const [modalVisible, setModalVisible] = useState(true); const openModal = function() { setModalVisible(true) }; const closeModal = function() { setModalVisible(false) }; return (  <>   <button onClick={openModal}>Click</div>   <Modal visible={modalVisible} onClose={closeModal} header="Create a modal">    <p>This is my content</p>   </Modal>  </> );}export default App;

這里使用了 hooks,請升級到最新版本的 react 來體驗。

實際上,一個完整的模態框組件還應該提供一些額外的配置來方便用戶使用,比如 header 和 content 的自定義樣式 headerClassName,contentClassName,定制操作按鈕的 footer,控制是否顯示關閉按鈕的 showClose 等等,
但這里為了保持教程的簡單,這些簡單的配置就不一一實現了,如果感興趣可以自行練習。

確定了我們的模態框的調用方式,現在我們來總結一下完整的模態框應該具備那些特性:

  1. 模態框組件應該掛載在 body 的第一層中,不要將模態框放置到父組件中,因為模態框放置到父組件中很容易受到其他元素的干擾。
  2. 模態框顯示后,模態框背后的背景不能隨著鼠標滾輪而滾動。
  3. 點擊模態框的遮罩層后,應該關閉模態框。

基礎功能

上面分析玩模態框的功能后,讓我們先開始實現一版最基礎的模態框。從 HTML 結構上來講,模態框組件分為 overlay 遮罩層和 content 內容兩部分組成,其中 content 里面還應該分為 header, content, footer(這里我們沒有實現)三部分組成。
所以,模態框的最基本的結構如下

import React, { PureComponent } from 'react';class Modal extends PureComponent { render() {  const { visible, onClose, header, children } = this.props;  return (   <div className={`overlay ${visible ? 'visible' : ''}`}>    <div className="content">     <div className="header">      {header}      <button onClick={onClose}>Close</button>     </div>     <div className="content">{children}</div>    </div>   </div>  ); }}

由于 overlay 元素是模態框組件的最外層的容器,所以我們可以通過控制 overlay 的顯示和隱藏(在上面的基礎結構中,通過 visible 屬性的值來給 overlay 添加或刪除類 'visible' 來控制 )實現模態框的打開關閉效果。在這里我們使用 display 實現控制 overlay 的顯示和隱藏(這樣在關閉時并沒有刪除該模態框,方便下次打開可以保存內容),同時 overlay 還是一個占據整個窗口的半透明暗色背景,所以 overlay 的樣式應該為

.overlay { display: none; position: fixed; top: 0; right: 0; bottom: 0; right: 0; background: rgba(0, 0, 0, 0.3); visibility: hidden;}.overlay.visible { display: block; visibility: visible;}

然后就是 content 中元素的樣式,都很簡單,大家看一下就好了,可以根據自己的組件規范修改這些樣式。

.container { margin: 80px auto; width: 80%; min-height: 800px; background: #fff; border-radius: 4px;}.header { display: flex; justify-content: space-between; padding: 16px; font-size: 24px; border-bottom: 1px solid #d3d3d3;}.body { padding: 16px;}.closeBtn { outline: none; border: none; appearance: none; font-size: 18px; color: #d5d5d5; cursor: pointer;}

這樣,我們最基礎的一版模態框就做好了,但是這個模態框是渲染在父組件中,那么如何才能將這個模態框放到 body 下,作為頂層元素呢?我們可以使用 Portal 這個 React 新提供的功能。

使用 portal 將模態框送到 body 中

Portal 是 React 16 中的新功能,就像它的名稱傳送門一樣,這個功能的作用就是將組件的 DOM 嗖的一下傳送到另外一個地方,換句話說就是可以讓你的組件渲染到其他地方,而不僅僅是在父組件中。從上面的描述中,我們知道 Portal 是一個作用于 DOM 的功能,所以 Portal 就在 react-dom 這個包下,react-dom 提供了 createPortal 方法來創建 Portal,它的第一參數是 React 組件,第二個參數則是接收這個組件的 DOM 節點。

回到我們的模態框來,為了方便的使用 Portal,我們首先創建一個 ModalPortal 組件,該組件會首先使用 createElement 創建一個表示 overlay 的 div,并使用 appendChild 將此 div 插入到 body 的末尾中,然后在 render 中,使用 createPortal 將 ModalPortal 接受的所有子組件送入 overlay 這個 div 中。通過這種方式,我們就把模態框組件變成 body 中的頂層元素了。

由于 overlay 是手動創建的 DOM 元素,所以當 visible 發生變化時,我們需要使用 DOM API 來控制 overlay 的顯示和隱藏,所以我們在 ModalPortal 組件的 componetDidMount 和 componetDidUpdate 兩個生命周期中,根據 visible 的值來增刪 overlay 的 visible 類控制 overlay 的顯示/隱藏。

import React, { PureComponent } from 'react';import { createPortal } from 'react-dom'class ModalPortal extends PureComponent { constructor(props) {  super(props);  // createElement 是一個封裝后的函數,方便在創建元素時添加屬性  this.node = createElement('div', {   class: `modal-${random()} ${props.className}`,  });  document.body.appendChild(this.node); } componentDidMount() {   this.checkIfVisible();  } componentDidUpdate(prevProps) {  if (prevProps.visible !== this.props.visible) {   this.checkIfVisible();  } } // 控制 overlay 的顯示隱藏 checkIfVisible = () => {  const { visible } = this.props;  if (visible) {   this.node.classList.add(styles.visible);  } else {   this.node.classList.remove(styles.visible);  } }; render() {  const { children } = this.props;  return createPortal(children, this.node); }}class Modal extends PureComponent { ... render() {  return (   <ModalPortal className='overlay' overlay={overlay}>    ...   </ModalPortal>  ) }}

阻止背景滾動

當我們完成上面的編碼之后,我們的模態框就可以實現顯示/隱藏,并且處于 body 的頂層,但是還有一個問題,那就是如果 body 內容太長出現滾動時,滾動鼠標就會發現,模態框后邊的背景也在滾動,這顯然不是我們希望的結果。如何應對這種情況呢?

解決辦法很巧妙,就是在模態框打開時,我們給 body 添加一個 overflow: hidden 的樣式讓 body 不滾動,然后關閉模態框再去除這個屬性。通過這樣的方式,我們就是實現在模態框打開時背景不滾動的功能了。
明白來原理之后就開始修改代碼了,我們首先在 constructor 中使用一個變量 savedBodyOverflow 來保持 body 原始的 overflow 值,然后修改 checkIfVisble 使之可以控制 overflow 類的增刪。

class ModalPortal extends PureComponent { constructor(props) {  ...  this.savedBodyOverflow = document.body.style.overflow; } ... checkIfVisible = () => {  const { visible } = this.props;  if (visible) {   this.node.classList.add(styles.visible);   document.body.style.overflow = 'hidden';  } else {   this.node.classList.remove(styles.visible);   document.body.style.overflow = this.saveBodyOverflow;  } }}

點擊遮罩層關閉

點擊遮罩層關閉,這個應該很容易實現,給 overlay 添加一個點擊事件監聽就好了,但是要注意一點就是,當你點擊遮罩層中的 content 時,不應當關閉。我們先回顧一下 DOM2 事件模型中的規定的事件流,事件從 window 開始,執行捕獲過程,然后到目標階段,接著執行冒泡過程,回到 window,這個流程就導致我們如果點擊了 content,overlay 同樣也會觸發點擊事件(DOM 2 默認冒泡階段觸發事件)。針對這種情況,我們可以使用事件中提供的 path 屬性,該屬性描述了事件冒泡過程中從目標元素的 window 的一個路徑,所以通過 path 的第一個參數,我們就可以判斷這個 click 是哪個元素觸發的了。

在我們的 modal 中,如果要實現點擊遮罩層關閉,我們可以監聽 overlay 元素的點擊事件,然后通過 path 屬性判斷事件是否是 overlay 觸發的,是否應該關閉模態框。因為 overlay 的 div 使我們自己生產的所以在 constructor 過程中就可以綁定事件了,注意在 componentWillUnMount 中要記得清除綁定,為了關閉模態框,別忘記將 onClose 通過 props 傳遞給 ModalPortal 組件。

class ModalPortal extends PureComponent { constructor(props) {  ...  this.node.addEventListener('click', this.handleClick); } componentWillUnmount() {  this.node.removeEventListener('click', this.handleClick); } handleClick = e => {  const { closeModal } = this.props;  const target = e.path[0];  if (target === this.node) {   onClose();  } }; ...}

按下 ESC 關閉

上面我們實現了點擊遮罩層關閉模態框,然后我們應該實現按下 ESC 關閉這個功能。通點擊事件一樣,我們只需要監聽 keydown 事件就可以了,這一次不用考慮到底是哪里觸發的問題了,只要 overlay 監聽到 keydown 就關閉模態框。但是這里也有一個小問題,就是 overlay 是 div,默認是監聽不到 keydown 事件的,對于這個問題,我們可以給 div 添加一個 tabIndex: 0 的屬性,通過指定 tabIndex,將 div 賦予 focusable 的能力,當模態框打開后,我們手動調用 focus 將焦點放到 overlay 上,這樣就能監聽到鍵盤事件。

const ESC_KEY = 27;class ModalPortal extends PureComponent { constructor(props) {  ...  this.node = createElement('div', {   class: `modal-${random()} ${props.className}`,   tabIndex: 0,  });  this.node.addEventListener('keydown', this.handleKeyDown); } componentWillUnmount() {  ...   this.node.removeEventListener('keydown', this.handleKeyDown); } checkIfVisible = () => {  const { visible } = this.props;  if (visible) {   ...   this.node.focus();  } else {   ...  } }; handleKeyDown = e => {  const { closeModal } = this.props;  if (e.keyCode === ESC_KEY) {   closeModal();  } }; ...}

消除滾動條導致的頁面抖動

在上面的防止遮罩層后面背景滾動是通過在 body 上設置 overflow: hidden 來防止滾動,但是如果 body 已經有了滾動條,那么 overflow 屬性會造成滾動條消失。滾動條在 chrome 上為 15px,打開和關閉模態框會使頁面不停地對這 15px 做處理,導則頁面抖動。為了防止抖動,我們可以在滾動條消失后給 body 添加 15px 的右邊距,滾動條出現后在刪除右邊距,通過這樣的方法,頁面就不會發生抖動了。

因為各個瀏覽器的標準不一致,所以我們應該想辦法計算出滾動條的寬度。為了計算出滾動條的寬度,我們可以使用 innerWidth 和 offsetWidth 這兩個屬性。offsetWidth 是包含邊框的長度,理所當然的包含了滾動條的寬度,只需要使用 offsetWidth 減去 innerWidth,得到的差值就是滾動條的寬度了。我們可以手動創建一個隱藏的有寬度的且有滾動條的元素,然后通過這個元素來獲取滾動條的寬度。

const calcScrollBarWidth = function() { const testNode = createElement('div', {  style: 'visibility: hidden; position: absolute; width: 100px; height: 100px; z-index: -999; overflow: scroll;' }); document.body.appendChild(testNode); const scrollBarWidth = testNode.offsetWidth - testNode.clientWidth; document.body.removeChild(testNode); return scrollBarWidth;};const preventJitter = function() { const scrollBarWidth = calcScrollBarWidth(); if (parseInt(document.documentElement.style.marginRight) === scrollBarWidth) {  document.documentElement.style.marginRight = 0; } else {  document.documentElement.style.marginRight = scrollBarWidth + 'px'; }};

結語

我們上面討論了做好一個模態框所需要考慮的技術,但是肯定還有不完善和錯誤的地方,所以,如果錯誤的地方請給我提 issue 我會盡快修正。代碼

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品第二页| 91国语精品自产拍在线观看性色| 亚洲精品永久免费精品| 欧美丝袜美女中出在线| 丝袜亚洲另类欧美重口| 亚洲国产精品99久久| 国产91露脸中文字幕在线| 成人亚洲激情网| 在线观看免费高清视频97| 日韩视频在线一区| 久久艳片www.17c.com| 午夜精品一区二区三区在线播放| 一本色道久久88精品综合| 一本大道久久加勒比香蕉| 欧洲成人性视频| 亚洲一区二区免费| 欧美多人乱p欧美4p久久| 欧美成人全部免费| 91精品国产91久久久久久最新| 日本成人在线视频网址| 日韩中文字幕视频在线| 欧美大片免费观看在线观看网站推荐| 日韩视频第一页| 在线成人激情黄色| 欧美专区在线播放| 欧美日韩中文在线| 亚洲毛片一区二区| 精品中文视频在线| 精品欧美激情精品一区| 免费91在线视频| 欧美成人午夜剧场免费观看| 国产视频精品自拍| 欧美激情图片区| 欧美激情日韩图片| 热久久视久久精品18亚洲精品| 日韩久久免费视频| 日本精品性网站在线观看| 欧美最顶级丰满的aⅴ艳星| 久久噜噜噜精品国产亚洲综合| 久久久精品网站| 亚洲激情自拍图| 九九久久久久99精品| 欧美大成色www永久网站婷| 精品中文视频在线| 成人网页在线免费观看| 国产精品视频内| 欧美黄色片免费观看| 欧美激情xxxx性bbbb| 伊人久久久久久久久久久久久| 国产成人一区二区| 国产美女直播视频一区| 亚洲伊人一本大道中文字幕| 欧美激情在线有限公司| 97免费在线视频| 一区二区三区日韩在线| 亚洲欧美国产va在线影院| 欧美激情一级欧美精品| 亚洲综合日韩中文字幕v在线| 国产精品精品一区二区三区午夜版| 精品国产区一区二区三区在线观看| 国产做受69高潮| 精品露脸国产偷人在视频| 亚洲视频在线观看免费| 国产999在线观看| 韩剧1988在线观看免费完整版| 这里精品视频免费| 亚洲男人的天堂网站| www.亚洲一区| 91美女片黄在线观看游戏| 亚洲国产女人aaa毛片在线| 国产精品人成电影| 北条麻妃久久精品| 81精品国产乱码久久久久久| 日韩欧美在线免费观看| 欧美日韩国产麻豆| 97在线观看视频| 国产精品久久久久久久久久久新郎| 亚洲精品国精品久久99热| 国产精品欧美一区二区三区奶水| 久久精品视频在线播放| 国产成人精品视频在线观看| 亚洲午夜精品视频| 成人福利在线视频| 久久91亚洲人成电影网站| 亚洲欧美制服综合另类| 日本a级片电影一区二区| 91精品国产91久久| 久热精品在线视频| 国产精品久久久久91| 精品视频在线导航| 亚洲性线免费观看视频成熟| 国产精品久久久久久影视| 精品久久久久久亚洲精品| 日本亚洲欧洲色| 亚洲色图校园春色| 国产成人精品久久久| 精品无人区乱码1区2区3区在线| 日韩在线中文字幕| 热re99久久精品国产66热| 久久久亚洲精品视频| 国产拍精品一二三| 欧美黑人巨大精品一区二区| 亚洲欧洲午夜一线一品| 91伊人影院在线播放| 日韩av在线直播| 欧美性猛交xxxx免费看| 亚洲精品视频免费在线观看| 日本久久中文字幕| 日本中文字幕不卡免费| www.精品av.com| 亚洲第一精品夜夜躁人人躁| 91精品久久久久久久久| 国产精品久久久久久久久久免费| 国产精品美女久久久久av超清| 97在线观看免费高清| 日本久久中文字幕| 57pao成人国产永久免费| 日韩免费在线观看视频| 成人免费淫片视频软件| 国产精品第一视频| 日本欧美在线视频| 欧美亚洲国产视频| 亚洲精品国产suv| 国产在线a不卡| 久久久在线观看| 中文字幕亚洲一区二区三区| 久久精品国产亚洲一区二区| 精品夜色国产国偷在线| 欧美性jizz18性欧美| 色久欧美在线视频观看| 国产丝袜精品第一页| 国产视频精品一区二区三区| 欧美性在线观看| 亚洲精品欧美一区二区三区| 久久久久久久91| 久久精品99久久久久久久久| 欧美精品xxx| 日韩高清人体午夜| 美女久久久久久久久久久| 日本免费一区二区三区视频观看| 韩国v欧美v日本v亚洲| 午夜剧场成人观在线视频免费观看| 久久影院在线观看| 日韩av电影手机在线| 国产成人午夜视频网址| 精品一区电影国产| 久久久久女教师免费一区| 亚洲精品福利在线| 精品亚洲夜色av98在线观看| 亚洲成年人在线| 中文字幕久热精品在线视频| 日韩精品999| 欧美成人中文字幕在线| 国产成人涩涩涩视频在线观看| 欧美日韩一区二区免费视频| 97国产一区二区精品久久呦| 国内揄拍国内精品| 久久视频在线播放| 久久久极品av| 国产精欧美一区二区三区| 精品国产福利在线| 性色av一区二区三区| 久久夜色精品亚洲噜噜国产mv| 亚洲最大激情中文字幕|