# React(MRWR)第 14 節:Creating Protals with ReactDOM > Udemy課程:[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/) `20240131Wed.~20240202Fri.` :::danger 14-240 createPortal ::: ## 14-236. Modal Component Overview Modal-互動視窗。 這章節要先做一個button:  點擊之後,會跳出一個互動視窗,他會置中在畫面。而互動視窗的後方會有一個較暗的背景,若使用者想關掉這個互動視窗,可以透過點擊互動視窗上的某個按鈕,或者直接點擊互動式創外的背景:  實作步驟:  **** ## 14-237. Toggling Visibility 按鈕按下去會顯示互動視窗,也可以關掉互動視窗,代表內容有所變動,所以我們會需要用到state!  這裡會先設置一個按鈕,但問題來了,我們應該用Modal元件來顯示呢?還是用他們的parent來顯示這按鈕?以及這個變換的state又要定義在哪裡呢?  所以來列出兩種想法來看看,到底怎樣合適。 第一種,無論按鈕或是state,全部設在Modal元件上。  不過,我們可能會希望這個Modal不一定是在使用者按下按鈕才出現,我們也會希望maybe使用者訪問我們頁面後的10秒,跳出這個互動視窗,讓使用者註冊之類的,所以第一種大概就不合適了,來看看第二種!  他的想法如下所示:(此處的parent其實就是指ModalPage)  **** ## 14-238. At First Glance, Easy! 老師在一開始便提到顯示互動視窗這件事情,一開始頗簡單的,但事實上我們會遇到以下困難點需要克服,這將會在後面幾堂課來處理  初版實作:  **** ## 14-239. We're Lucky it Works At All!  Position的屬性:(預設為static)  Absolute規則:  問題來了,上圖中的灰色div跟藍色div會呈現在什麼位置呢? 答案如下所示:  再來看看inset-0的規則:  問題又來了,上圖中的綠色div跟藍色div會呈現在什麼位置呢? 答案如下所示:   而從上一章節完成的code來說,會成功的原因是因為很剛好的沒有其他parent擁有其他的position屬性,所以整個灰色背景就能況章整個html document。   也就是說只有我們的Modal元件外的任一parent,若擁有任一position屬性(預設static之外的),都會讓灰色背景無法填滿整個html document。 **** ## 14-240. Fixing the Modal with Portals  為了保證之後即便加了position在Modal元件的外層,也可以不破壞整個灰色div覆蓋整個html document,我們這裡會用到一個React的特殊功能,叫做"Portals"。 > 官方文件:[createPortal](https://react.dev/reference/react-dom/createPortal) Portals會告訴React去做稍微不一樣的事情。他可以讓我們的Modal元件跑到html document的其他地方:  那為什麼這樣移動後,就不會出問題了呢?因為...  所以該怎麼實作呢?接下來,我們將不會只是在元件中return純JSX,,而是會呼叫一個函式名為"ReactDOM.createPortal()",而Portals將會告訴React將我們的html渲染至其他DOM裡面。 "ReactDOM.createPortal()"接收兩個參數,第一個參數為我們要render的html,第二個參數則是我們想把這個html渲染到「index.html file」中的什麼地方去? :::warning [注意!!] * 要記得先到index.html中,先建立一個div叫做`<div class="modal-container"></div>` * 要使用ReactDOM,要記得先import: `import ReactDOM from "react"` :::  :::warning 目前使用ceratePortal的方式大致上有以下兩種: (這一堂課底下剛好有人詢問) ```javascript! import { createPortal } from "react-dom" ... return createPortal( ``` vs: ```javascript! import ReactDOM from 'react-dom'; ... return ReactDOM.createPortal( ``` ::: **** ## 14-241. Closing the Modal 再來要實作把這個互動視窗給關掉的功能。首先,先來完成「點擊灰色背景即可關掉」的功能。 實作想法:  實作整個過程:  **** ## 14-242. Customizing the Modal  **** ## 14-244. One Small Bug 產生假文章的網站:[Lipsum](https://www.lipsum.com/) **** ## 14-245. Modal Wrapup * 問題1: 加上一對假文章後,我們網頁出現了scroll,此時若打開互動視窗會發現往下滑時,會......  * 解決想法: 當打開互動視窗時,在body加上`overflow: hidden`,讓頁面無法滑動,而當關掉互動視窗時,就把這個樣式給移除。 * 實作辦法: 利用`useEffect` hook,當Modal元件被創造(或說被安裝)後,就會觸發`useEffect`幫body加上`overflow: hidden`,我們在第二個參數設置`[]`,這樣他只有第一次render時才會被觸發 另外在useEffect中return,這樣當Modal元件被移除銷毀(或說被卸載),那麼就會return,return什麼呢?就是把`overflow: hidden`從body上移除。 //Modal ```javascript! import ReactDOM from "react-dom"; import { useEffect, useReducer } from "react"; function Modal({ onClose, children, actionBar}){ //只有在Modal顯現出來useEffect才會作用 useEffect(() => { document.body.classList.add("overflow-hidden"); return () => { document.body.classList.remove("overflow-hidden"); } }, []) return ReactDOM.createPortal( <div> <div onClick={onClose} className="absolute inset-0 bg-gray-300 opacity-80"></div> <div className="absolute inset-40 p-10 bg-white"> <div className="flex flex-col justify-between h-full"> {children} <div className="flex justify-end"> {actionBar} </div> </div> </div> </div>, document.querySelector(".modal-container") ); } export default Modal; ``` **** * 問題2: 假如button在網頁最下方,所以使用者必須先 scroll down到底下,此時若點擊按鈕打開互動視窗時,會發生... 互動視窗是打開了,也加上了`overflow: hidden`在body上,所以也無法滾動,但是互動視窗會緊貼著網頁最上方,所以我們無法看到完整的互動視窗,當然灰色背景也被卡一半。  * 解決想法: 這是因為我們有對Modal下absolute,所以他會黏在body最上方及最左側,所以不應該用absolute * 實作辦法: 將absolute用fixed替代即可。 > fixed參考:[position - 金魚都能懂的CSS必學屬性](https://ithelp.ithome.com.tw/articles/10253500) > > 「fixed 固定定位」的效果是將設定的物件,將其參考空間設定為「視窗」,也就是說當我對一個物件設定了 position: fixed; 之後,該物件的參考空間就直接對視窗的範圍了,需要特別注意的是 fixed 定位與 absolute ,一樣都會自己獨立一層,所以這兩者非常容易被不少人誤會是相同的,但其實特性的細節差異很多阿!
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up