中秋放假,一我的有点无聊,因而写点博文暖暖心,同时祝你们中秋快乐~ 🙃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~