中秋放假,一我的有點無聊,因而寫點博文暖暖心,同時祝你們中秋快樂~ 🙃javascript
接下來將一步步帶領你們實現一個基本的modal彈窗組件,封裝一個簡單的動畫組件,其中涉及到的一些知識點也會在代碼中予以註釋講解。css
咱們使用create-react-app指令,快速搭建開發環境:java
create-react-app modal
複製代碼
安裝完成後,按照提示啓動項目,接着在src
目錄下新建modal
目錄,同時建立modal.jsx modal.css
兩個文件node
modal.jsx
內容以下:react
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
render() {
return <div className="modal"> 這是一個modal組件 </div>
}
}
export default Modal;
複製代碼
回到根目錄,打開App.js,將其中內容替換成以下:css3
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return <div className="app"> <Modal></Modal> </div>
}
}
export default App;
複製代碼
完成以上步驟後,咱們瀏覽器中就會以下圖顯示了:git
寫以前,咱們先回想一下,咱們平時使用的modal組件都有哪些元素,一個標題區,內容區,還有控制區,一個mask;github
modal.jsx
內容修改以下:redux
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
render() {
return <div className="modal-wrapper"> <div className="modal"> <div className="modal-title">這是modal標題</div> <div className="modal-content">這是modal內容</div> <div className="modal-operator"> <button className="modal-operator-close">取消</button> <button className="modal-operator-confirm">確認</button> </div> </div> <div className="mask"></div> </div>
}
}
export default Modal;
複製代碼
modal.css
內容修改以下:瀏覽器
.modal {
position: fixed;
width: 300px;
height: 200px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
border-radius: 5px;
background: #fff;
overflow: hidden;
z-index: 9999;
box-shadow: inset 0 0 1px 0 #000;
}
.modal-title {
width: 100%;
height: 50px;
line-height: 50px;
padding: 0 10px;
}
.modal-content {
width: 100%;
height: 100px;
padding: 0 10px;
}
.modal-operator {
width: 100%;
height: 50px;
}
.modal-operator-close, .modal-operator-confirm {
width: 50%;
border: none;
outline: none;
height: 50px;
line-height: 50px;
opacity: 1;
color: #fff;
background: rgb(247, 32, 32);
cursor: pointer;
}
.modal-operator-close:active, .modal-operator-confirm:active {
opacity: .6;
transition: opacity .3s;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: .6;
z-index: 9998;
}
複製代碼
修改完成後,咱們瀏覽器中就會以下圖顯示:
到這裏咱們的準備工做已經完成,接下就具體實現modal功能,再次回想,咱們使用modal組件的時候,會有哪些基本的功能呢?
visible
控制modal
的顯隱;title
,content
能夠自定義顯示內容;modal
,同時會調用名爲onClose
的回調,點擊確認會調用名爲confirm
的回調,並關閉modal
,點擊蒙層mask
關閉modal
;visible
字段控制顯隱modal.jsx
修改以下:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
}
render() {
// 經過父組件傳遞的visile控制顯隱
const { visible } = this.props;
return visible && <div className="modal-wrapper"> <div className="modal"> <div className="modal-title">這是modal標題</div> <div className="modal-content">這是modal內容</div> <div className="modal-operator"> <button className="modal-operator-close">取消</button> <button className="modal-operator-confirm">確認</button> </div> </div> <div className="mask"></div> </div>
}
}
export default Modal;
複製代碼
App.js
修改以下:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
// 這裏綁定this由於類中的方法不會自動綁定指向當前示例,咱們須要手動綁定,否則方法中的this將是undefined,這是其中一種綁定的方法,
// 第二種方法是使用箭頭函數的方法,如:showModal = () => {}
// 第三種方法是調用的時候綁定,如:this.showModal.bind(this)
this.showModal = this.showModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
render() {
const { visible } = this.state
return <div className="app"> <button onClick={this.showModal}>click here</button> <Modal visible={visible}></Modal> </div>
}
}
export default App;
複製代碼
以上咱們經過父組件App.js
中的visible狀態,傳遞給modal
組件,再經過button
的點擊事件來控制visible的值以達到控制modal
組件顯隱的效果
未點擊按鈕效果以下圖:
點擊按鈕後效果以下圖:
title
與content
內容自定義modal.jsx
修改以下:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
}
render() {
const { visible, title, children } = this.props;
return visible && <div className="modal-wrapper"> <div className="modal"> {/* 這裏使用父組件的title*/} <div className="modal-title">{title}</div> {/* 這裏的content使用父組件的children*/} <div className="modal-content">{children}</div> <div className="modal-operator"> <button className="modal-operator-close">取消</button> <button className="modal-operator-confirm">確認</button> </div> </div> <div className="mask"></div> </div>
}
}
export default Modal;
複製代碼
App.js
修改以下:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.showModal = this.showModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
render() {
const { visible } = this.state
return <div className="app"> <button onClick={this.showModal}>click here</button> <Modal visible={visible} title="這是自定義title" > 這是自定義content </Modal> </div>
}
}
export default App;
複製代碼
接着咱們點擊頁面中的按鈕,結果顯示以下:
寫前思考:咱們須要點擊取消按鈕關閉
modal
,那麼咱們就須要在modal
中維護一個狀態,而後用這個狀態來控制modal
的顯隱,好像可行,可是咱們再一想,咱們前面是經過父組件的visible
控制modal
的顯隱,這樣不就矛盾了嗎?這樣不行,那咱們做一下改變,若是父組件的狀態改變,那麼咱們只更新這個狀態,modal
中點擊取消咱們也只更新這個狀態,最後用這個狀態值來控制modal
的顯隱;至於onClose
鉤子函數咱們能夠再更新狀態以前進行調用,確認按鈕的點擊同取消。
modal.jsx
修改以下:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
// 首次渲染使用父組件的狀態更新modal中的visible狀態,只調用一次
componentDidMount() {
this.setState({ visible: this.props.visible })
}
// 每次接收props就根據父組件的狀態更新modal中的visible狀態,首次渲染不會調用
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
// 點擊取消更新modal中的visible狀態
closeModal() {
console.log('你們好,我叫取消,據說大家想點我?傲嬌臉👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('你們好,我叫確認,樓上的取消是我兒子,腦子有點那個~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('你們好,我是蒙層,我被點擊了')
this.setState({ visible: false})
}
render() {
// 使用modal中維護的visible狀態來控制顯隱
const { visible } = this.state;
const { title, children } = this.props;
return visible && <div className="modal-wrapper"> <div className="modal"> <div className="modal-title">{title}</div> <div className="modal-content">{children}</div> <div className="modal-operator"> <button onClick={this.closeModal} className="modal-operator-close" >取消</button> <button onClick={this.confirm} className="modal-operator-confirm" >確認</button> </div> </div> <div className="mask" onClick={this.maskClick} ></div> </div>
}
}
export default Modal;
複製代碼
App.js
修改以下:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
closeModal() {
console.log('我是onClose回調')
}
confirm() {
console.log('我是confirm回調')
}
render() {
const { visible } = this.state
return <div className="app"> <button onClick={this.showModal}>click here</button> <Modal visible={visible} title="這是自定義title" confirm={this.confirm} onClose={this.closeModal} > 這是自定義content </Modal> </div>
}
}
export default App;
複製代碼
保存後,咱們再瀏覽器中分別點擊取消和確認,控制檯中將會出現以下圖所示:
以上就完成了一個基本的modal
組件,可是咱們還有一個疑問,就是如今引入的modal
是在類名爲App
的元素之中,而一些被普遍使用的UI框架中的modal
組件確實在body
層,不管你在哪裏引入,這樣就能夠防止modal
組件受到父組件的樣式的干擾。
而想要實現這種效果,咱們必須得先了解React自帶的特性:Portals
(傳送門)。這個特性是在16版本以後添加的,而在16版本以前,都是經過使用ReactDOM
的unstable_renderSubtreeIntoContainer
方法處理,這個方法能夠將元素渲染到指定元素中,與ReactDOM.render
方法的區別就是,能夠保留當前組件的上下文context
,react-redux
就是基於context
進行跨組件之間的通訊,因此如果使用ReactDOM.render
進行渲染就會致使丟失上下文,從而致使全部基於context
實現跨組件通訊的框架失效。
ReactDOM.unstable_renderSubtreeIntoContainer
的使用ReactDOM.unstable_renderSubtreeIntoContainer(
parentComponent, // 用來指定上下文
element, // 要渲染的元素
containerNode, // 渲染到指定的dom中
callback // 回調
);
複製代碼
接下來在咱們的項目中使用它,src
目錄下新建oldPortal
目錄,並在其中新建oldPortal.jsx
,oldPortal.jsx
中的內容以下:
import React from 'react';
import ReactDOM from 'react-dom';
class OldPortal extends React.Component {
constructor(props) {
super(props)
}
// 初始化時根據visible屬性來判斷是否渲染
componentDidMount() {
const { visible } = this.props
if (visible) {
this.renderPortal(this.props);
}
}
// 每次接受到props進行渲染與卸載操做
componentWillReceiveProps(props) {
if (props.visible) {
this.renderPortal(props)
} else {
this.closePortal()
}
}
// 渲染
renderPortal(props) {
if (!this.node) {
// 防止屢次建立node
this.node = document.createElement('div');
}
// 將當前node添加到body中
document.body.appendChild(this.node);
ReactDOM.unstable_renderSubtreeIntoContainer(
this, // 上下文指定當前的實例
props.children, // 渲染的元素爲當前的children
this.node, // 將元素渲染到咱們新建的node中,這裏咱們不使用第四個參數回調.
);
}
// 卸載
closePortal() {
if (this.node) {
// 卸載元素中的組件
ReactDOM.unmountComponentAtNode(this.node)
// 移除元素
document.body.removeChild(this.node)
}
}
render() {
return null;
}
}
export default OldPortal
複製代碼
保存後,咱們在modal.jsx
中使用它:
import React, { Component } from 'react';
import OldPortal from '../oldPortal/oldPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('你們好,我叫取消,據說大家想點我?傲嬌臉👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('你們好,我叫確認,樓上的取消是我兒子,腦子有點那個~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('你們好,我是蒙層,我被點擊了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <OldPortal visible={visible}> <div className="modal-wrapper"> <div className="modal"> <div className="modal-title">{title}</div> <div className="modal-content">{children}</div> <div className="modal-operator"> <button onClick={this.closeModal} className="modal-operator-close" >取消</button> <button onClick={this.confirm} className="modal-operator-confirm" >確認</button> </div> </div> <div className="mask" onClick={this.maskClick} ></div> </div> </OldPortal>
}
}
export default Modal;
複製代碼
能夠看到,咱們僅僅是在modal
中return
的內容外層包裹一層OldPortal
組件,而後將控制顯隱的狀態visible
傳遞給了OldPortal
組件,由OldPortal
來實際控制modal
的顯隱;而後咱們點擊頁面中的按鈕,同時打開控制檯,發現modal
如咱們所想,牀送到了body
層:
Portal
使用在16版本中,react-dom
原生提供了一個方法ReactDOM.createPortal()
,用來實現傳送門的功能:
ReactDOM.createPortal(
child, // 要渲染的元素
container // 指定渲染的父元素
)
複製代碼
參數比之unstable_renderSubtreeIntoContainer
減小了兩個,接着咱們在項目中使用它.
在src
目錄下新建newPortal
目錄,在其中新建newPortal.jsx
,newPortal.jsx
內容以下:
import React from 'react';
import ReactDOM from 'react-dom';
class NewPortal extends React.Component {
constructor(props) {
super(props)
// 初始化建立渲染的父元素並添加到body下
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
const { visible, children } = this.props;
// 直接經過顯隱表示
return visible && ReactDOM.createPortal(
children,
this.node,
);
}
}
export default NewPortal
複製代碼
能夠很清晰的看到內容對比unstable_renderSubtreeIntoContainer
的實現簡化了不少,而後咱們在modal.jsx
中使用:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('你們好,我叫取消,據說大家想點我?傲嬌臉👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('你們好,我叫確認,樓上的取消是我兒子,腦子有點那個~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('你們好,我是蒙層,我被點擊了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <NewPortal visible={visible}> <div className="modal-wrapper"> <div className="modal"> <div className="modal-title">{title}</div> <div className="modal-content">{children}</div> <div className="modal-operator"> <button onClick={this.closeModal} className="modal-operator-close" >取消</button> <button onClick={this.confirm} className="modal-operator-confirm" >確認</button> </div> </div> <div className="mask" onClick={this.maskClick} ></div> </div> </NewPortal>
}
}
export default Modal;
複製代碼
使用上與OldPortal
同樣,接下來看看瀏覽器中看看效果是否如咱們所想:
能夠說Portals
是彈窗類組件的靈魂,這裏對Portals
的使用僅僅是做爲一個引導,講解了其核心功能,並無深刻去實現一些複雜的公共方法,有興趣的讀者能夠搜索相關的文章,都有更詳細的講解.
從一個簡單的效果開始(使用的代碼是以上使用NewPortal
組件的Modal
組件),modal
彈出時逐漸放大,放大到1.1倍,最後又縮小到1倍,隱藏時,先放大到1.1倍,再縮小,直到消失.
慣例先思考: 咱們經過控制什麼達到放大縮小的效果?咱們如何將放大和縮小這個過程從瞬間變爲一個漸變的過程?咱們在何時開始放大縮小?又在何時結束放大縮小?
放大和縮小咱們經過css3
的屬性transform scale
進行控制,漸變的效果使用transition
過分彷佛是不錯的選擇,而放大縮小的時機,分爲元素開始出現,出現中,出現結束,開始消失,消失中,消失結束六種狀態,而後咱們分別定義這六種狀態的scale
參數,再使用transition
進行過分,應該就能實現咱們須要的效果了:
再modal.css
添加以下代碼:
.modal-enter {
transform: scale(0);
}
.modal-enter-active {
transform: scale(1.1);
transition: all .2s linear;
}
.modal-enter-end {
transform: scale(1);
transition: all .1s linear;
}
.modal-leave {
transform: scale(1);
}
.modal-leave-active {
transform: scale(1.1);
transition: all .1s linear;
}
.modal-leave-end {
transform: scale(0);
transition: all .2s linear;
}
複製代碼
六種類名分別定義了出現與消失的六種狀態,同時設置了各自的過分時間,接下來咱們就在不一樣的過程給元素添加對應的類名,就能控制元素的顯示狀態了.
在咱們寫邏輯以前,咱們還須要注意一點,以前咱們組件的顯隱是在NewPortal
組件中實際控制的,可是咱們在Modal
組件中添加動畫,就須要嚴格掌控顯隱的時機,好比剛渲染就要開始動畫,動畫結束以後才能隱藏,這樣就不適合在NewPortal
組件中控制顯隱了.有的讀者就疑惑了,爲何不直接在NewPortal
組件中添加動畫呢?固然這個問題的答案是確定的,可是NewPortal
的功能是傳送,並不複雜動畫,咱們要保持它的純淨,不宜與其餘組件耦合.
修改newPortal.jsx
的內容以下:
import React from 'react';
import ReactDOM from 'react-dom';
class NewPortal extends React.Component {
constructor(props) {
super(props)
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
const { children } = this.props;
return ReactDOM.createPortal(
children,
this.node,
);
}
}
export default NewPortal
複製代碼
修改modal.jsx
的內容以下:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.leaveAnimate = this.leaveAnimate.bind(this)
this.enterAnimate = this.enterAnimate.bind(this)
this.state = {
visible: false,
classes: null,
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
if (props.visible) {
// 接收到父組件的props時,若是是true則進行動畫渲染
this.enterAnimate()
}
}
// 進入動畫
enterAnimate() {
// 這裏定義每種狀態的類名,就是咱們以前modal.css文件中添加的類
const enterClasses = 'modal-enter'
const enterActiveClasses = 'modal-enter-active'
const enterEndActiveClasses = 'modal-enter-end'
// 這裏定義了每種狀態的過分時間,對應着modal.css中對應類名下的transition屬性的時間,這裏的單位爲毫秒
const enterTimeout = 0
const enterActiveTimeout = 200
const enterEndTimeout = 100
// 將顯隱狀態改成true,同時將classes改成enter狀態的類名
this.setState({ visible: true, classes: enterClasses })
// 這裏使用定時器,是由於定時器中的函數會被加入到事件隊列,帶到主線程任務進行完成纔會被調用,至關於在元素渲染出來而且加上初始的類名後enterTimeout時間後開始執行.
// 由於開始狀態並不須要過分,因此咱們直接將之設置爲0.
const enterActiveTimer = setTimeout(_ => {
this.setState({ classes: enterActiveClasses })
clearTimeout(enterActiveTimer)
}, enterTimeout)
const enterEndTimer = setTimeout(_ => {
this.setState({ classes: enterEndActiveClasses })
clearTimeout(enterEndTimer)
}, enterTimeout + enterActiveTimeout)
// 最後將類名置空,還原元素原本的狀態
const initTimer = setTimeout(_ => {
this.setState({ classes: '' })
clearTimeout(initTimer)
}, enterTimeout + enterActiveTimeout + enterEndTimeout)
}
// 離開動畫
leaveAnimate() {
const leaveClasses = 'modal-leave'
const leaveActiveClasses = 'modal-leave-active'
const leaveEndActiveClasses = 'modal-leave-end'
const leaveTimeout = 0
const leaveActiveTimeout = 100
const leaveEndTimeout = 200
// 初始元素已經存在,因此不須要改變顯隱狀態
this.setState({ classes: leaveClasses })
const leaveActiveTimer = setTimeout(_ => {
this.setState({ classes: leaveActiveClasses })
clearTimeout(leaveActiveTimer)
}, leaveTimeout)
const leaveEndTimer = setTimeout(_ => {
this.setState({ classes: leaveEndActiveClasses })
clearTimeout(leaveEndTimer)
}, leaveTimeout + leaveActiveTimeout)
// 最後將顯隱狀態改成false,同時將類名還原爲初始狀態
const initTimer = setTimeout(_ => {
this.setState({ visible: false, classes: '' })
clearTimeout(initTimer)
}, leaveTimeout + leaveActiveTimeout + leaveEndTimeout)
}
closeModal() {
console.log('你們好,我叫取消,據說大家想點我?傲嬌臉👸')
const { onClose } = this.props
onClose && onClose()
// 點擊取消後調用離開動畫
this.leaveAnimate()
}
confirm() {
console.log('你們好,我叫確認,樓上的取消是我兒子,腦子有點那個~')
const { confirm } = this.props
confirm && confirm()
this.leaveAnimate()
}
maskClick() {
console.log('你們好,我是蒙層,我被點擊了')
this.setState({ visible: false })
}
render() {
const { visible, classes } = this.state;
const { title, children } = this.props;
return <NewPortal> <div className="modal-wrapper"> { visible && <div className={`modal ${classes}`}> <div className="modal-title">{title}</div> <div className="modal-content">{children}</div> <div className="modal-operator"> <button onClick={this.closeModal} className="modal-operator-close" >取消</button> <button onClick={this.confirm} className="modal-operator-confirm" >確認</button> </div> </div> } {/* 這裏暫時註釋蒙層,防止干擾 */} {/* <div className="mask" onClick={this.maskClick} ></div> */} </div> </NewPortal>
}
}
export default Modal;
複製代碼
效果以下:
實現了動畫效果,可是代碼所有在modal.jsx
中,一點也不優雅,並且也不能複用,所以咱們須要考慮將之抽象成一個Transition
組件。
思路:咱們從須要的功能點出發,來考慮如何進行封裝。首先傳入的顯隱狀態值控制元素的顯隱;給與一個類名,其能匹配到對應的六種狀態類名;能夠配置每種狀態的過渡時間;能夠控制是否使用動畫;
在src
目錄新建transition
目錄,建立文件transition.jsx
,內容以下:
import React from 'react';
// 這裏引入classnames處理類名的拼接
import classnames from 'classnames';
class Transition extends React.Component {
constructor(props) {
super(props)
this.getClasses = this.getClasses.bind(this)
this.enterAnimate = this.enterAnimate.bind(this)
this.leaveAnimate = this.leaveAnimate.bind(this)
this.appearAnimate = this.appearAnimate.bind(this)
this.cloneChildren = this.cloneChildren.bind(this)
this.state = {
visible: false,
classes: null,
}
}
// 過渡時間不傳入默認爲0
static defaultProps = {
animate: true,
visible: false,
transitionName: '',
appearTimeout: 0,
appearActiveTimeout: 0,
appearEndTimeout: 0,
enterTimeout: 0,
enterActiveTimeout: 0,
enterEndTimeout: 0,
leaveTimeout: 0,
leaveEndTimeout: 0,
leaveActiveTimeout: 0,
}
// 這裏咱們添加了首次渲染動畫。只出現一次
componentWillMount() {
const { transitionName, animate, visible } = this.props;
if (!animate) {
this.setState({ visible })
return
}
this.appearAnimate(this.props, transitionName)
}
componentWillReceiveProps(props) {
const { transitionName, animate, visible } = props
if (!animate) {
this.setState({ visible })
return
}
if (!props.visible) {
this.leaveAnimate(props, transitionName)
} else {
this.enterAnimate(props, transitionName)
}
}
// 首次渲染的入場動畫
appearAnimate(props, transitionName) {
const { visible, appearTimeout, appearActiveTimeout, appearEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('appear', transitionName)
this.setState({ visible, classes: initClasses })
setTimeout(_ => {
this.setState({ classes: activeClasses })
}, appearTimeout)
setTimeout(_ => {
this.setState({ classes: endClasses })
}, appearActiveTimeout + appearTimeout)
setTimeout(_ => {
this.setState({ classes: '' })
}, appearEndTimeout + appearActiveTimeout + appearTimeout)
}
// 入場動畫
enterAnimate(props, transitionName) {
const { visible, enterTimeout, enterActiveTimeout, enterEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('enter', transitionName)
this.setState({ visible, classes: initClasses })
const enterTimer = setTimeout(_ => {
this.setState({ classes: activeClasses })
clearTimeout(enterTimer)
}, enterTimeout)
const enterActiveTimer = setTimeout(_ => {
this.setState({ classes: endClasses })
clearTimeout(enterActiveTimer)
}, enterActiveTimeout + enterTimeout)
const enterEndTimer = setTimeout(_ => {
this.setState({ classes: '' })
clearTimeout(enterEndTimer)
}, enterEndTimeout + enterActiveTimeout + enterTimeout)
}
// 出場動畫
leaveAnimate(props, transitionName) {
const { visible, leaveTimeout, leaveActiveTimeout, leaveEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('leave', transitionName)
this.setState({ classes: initClasses })
const leaveTimer = setTimeout(_ => {
this.setState({ classes: activeClasses })
clearTimeout(leaveTimer)
}, leaveTimeout)
const leaveActiveTimer = setTimeout(_ => {
this.setState({ classes: endClasses })
clearTimeout(leaveActiveTimer)
}, leaveActiveTimeout + leaveTimeout)
const leaveEndTimer = setTimeout(_ => {
this.setState({ visible, classes: '' })
clearTimeout(leaveEndTimer)
}, leaveEndTimeout + leaveActiveTimeout + leaveTimeout)
}
// 類名統一配置
getClasses(type, transitionName) {
const initClasses = classnames({
[`${transitionName}-appear`]: type === 'appear',
[`${transitionName}-enter`]: type === 'enter',
[`${transitionName}-leave`]: type === 'leave',
})
const activeClasses = classnames({
[`${transitionName}-appear-active`]: type === 'appear',
[`${transitionName}-enter-active`]: type === 'enter',
[`${transitionName}-leave-active`]: type === 'leave',
})
const endClasses = classnames({
[`${transitionName}-appear-end`]: type === 'appear',
[`${transitionName}-enter-end`]: type === 'enter',
[`${transitionName}-leave-end`]: type === 'leave',
})
return { initClasses, activeClasses, endClasses }
}
cloneChildren() {
const { classes } = this.state
const children = this.props.children
const className = children.props.className
// 經過React.cloneElement給子元素添加額外的props,
return React.cloneElement(
children,
{ className: `${className} ${classes}` }
)
}
render() {
const { visible } = this.state
return visible && this.cloneChildren()
}
}
export default Transition
複製代碼
modal.jsx
內容修改以下:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import Transition from '../transition/transition';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false,
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('你們好,我叫取消,據說大家想點我?傲嬌臉👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('你們好,我叫確認,樓上的取消是我兒子,腦子有點那個~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('你們好,我是蒙層,我被點擊了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <NewPortal> {/* 引入transition組件,去掉了外層的modal-wrapper */} <Transition visible={visible} transitionName="modal" enterActiveTimeout={200} enterEndTimeout={100} leaveActiveTimeout={100} leaveEndTimeout={200} > <div className="modal"> <div className="modal-title">{title}</div> <div className="modal-content">{children}</div> <div className="modal-operator"> <button onClick={this.closeModal} className="modal-operator-close" >取消</button> <button onClick={this.confirm} className="modal-operator-confirm" >確認</button> </div> </div> {/* 這裏的mask也能夠用transition組件包裹,添加淡入淡出的過渡效果,這裏再也不添加,有興趣的讀者能夠本身實踐下 */} {/* <div className="mask" onClick={this.maskClick} ></div> */} </Transition> </NewPortal>
}
}
export default Modal;
複製代碼
文章到這裏就寫完了,爲了閱讀的完整性,每一個步驟都是貼的完整的代碼,致使全文篇幅過長,感謝您的閱讀。
本文代碼地址,歡迎star~