实现一个带有动效的 React 弹窗组件

开发 前端
虽然 React 中没有类似 Vue 官方定义的<transition>标签,不过我们可以自己或者借助第三方组件来实现。

[[406647]]

我们在写一些 UI 组件时,若不考虑动效,就很容易实现,主要就是有无的切换(类似于 Vue 中的 v-if 属性)或者可见性的切换(类似于 Vue 中的 v-show 属性)。

[[406648]]

1. 没有动效的弹窗

在 React 中,可以这样来实现:

  1. interface ModalProps { 
  2.   open: boolean
  3.   onClose?: () => void
  4.   children?: any; 
  5. const Modal = ({open. onClose, children}: ModalProps) => { 
  6.   if (!open) { 
  7.     return null
  8.   } 
  9.   return createPortal(<div> 
  10.     <div className="modal-content">{children}</div> 
  11.     <div className="modal-close-btn" onClick={onClose}>x</div> 
  12.   </div>, document.body); 
  13. }; 

使用方式:

  1. const App = () => { 
  2.   const [open, setOpen] = useState(false); 
  3.  
  4.   return ( 
  5.     <div className="app"
  6.       <button onClick={() => setOpen(true)}>show modal</button> 
  7.       <Modal open={open} onClose={() => setOpen(false)}> 
  8.         modal content 
  9.       </Modal> 
  10.     </div> 
  11.   ); 
  12. }; 

我们在这里就是使用 open 属性来控制展示还是不展示,但完全没有渐变的效果。

若我们想实现 fade, zoom 等动画效果,还需要对此进行改造。

[[406649]]

2. 自己动手实现有动效的弹窗

很多同学在自己实现动效时,经常是展示的时候有动效,关闭的时候没有动效。都是动效的时机没有控制好。这里我们先自己来实现一下动效的流转。

刚开始我实现的时候,动效只有开始状态和结束状态,需要很多的变量和逻辑来控制这个动效。

后来我参考了 react-transition-group 组件的实现,他是将动效拆分成了几个部分,每个部分分别进行控制。

  • 展开动效的顺序:enter -> enter-active -> enter-done;

  • 关闭动效的顺序:exit -> exit-active -> exit-done;

动效过程在 enter-active 和 exit-active 的过程中。

我们再通过一个变量 active 来控制是关闭动效是否已执行关闭,参数 open 只控制是执行展开动效还是关闭动效。

当 open 和 active 都为 false 时,才销毁弹窗。

  1. const Modal = ({ open, children, onClose }) => { 
  2.   const [active, setActive] = useState(false); // 弹窗的存在周期 
  3.  
  4.   if (!open && !active) { 
  5.     return null
  6.   } 
  7.  
  8.   return ReactDOM.createPortal( 
  9.     <div className="modal"
  10.       <div className="modal-content">{children}</div> 
  11.       <div className="modal-close-btn" onClick={onClose}> 
  12.         x 
  13.       </div> 
  14.     </div>, 
  15.     document.body, 
  16.   ); 
  17. }; 

这里我们接着添加动效过程的变化:

  1. const [aniClassName, setAniClassName] = useState(''); // 动效的class 
  2.  
  3. // transition执行完毕的监听函数 
  4. const onTransitionEnd = () => { 
  5.   // 当open为rue时,则结束状态为'enter-done' 
  6.   // 当open未false时,则结束状态为'exit-done' 
  7.   setAniClassName(open ? 'enter-done' : 'exit-done'); 
  8.  
  9.   // 若open为false,则动画结束时,弹窗的生命周期结束 
  10.   if (!open) { 
  11.     setActive(false); 
  12.   } 
  13. }; 
  14.  
  15. useEffect(() => { 
  16.   if (open) { 
  17.     setActive(true); 
  18.     setAniClassName('enter'); 
  19.     // setTimeout用来切换class,让transition动起来 
  20.     setTimeout(() => { 
  21.       setAniClassName('enter-active'); 
  22.     }); 
  23.   } else { 
  24.     setAniClassName('exit'); 
  25.     setTimeout(() => { 
  26.       setAniClassName('exit-active'); 
  27.     }); 
  28.   } 
  29. }, [open]); 

Modal 组件完整的代码如下:

  1. const Modal = ({ open, children, onClose }) => { 
  2.   const [active, setActive] = useState(false); // 弹窗的存在周期 
  3.   const [aniClassName, setAniClassName] = useState(''); // 动效的class 
  4.   const onTransitionEnd = () => { 
  5.     setAniClassName(open ? 'enter-done' : 'exit-done'); 
  6.     if (!open) { 
  7.       setActive(false); 
  8.     } 
  9.   }; 
  10.  
  11.   useEffect(() => { 
  12.     if (open) { 
  13.       setActive(true); 
  14.       setAniClassName('enter'); 
  15.       setTimeout(() => { 
  16.         setAniClassName('enter-active'); 
  17.       }); 
  18.     } else { 
  19.       setAniClassName('exit'); 
  20.       setTimeout(() => { 
  21.         setAniClassName('exit-active'); 
  22.       }); 
  23.     } 
  24.   }, [open]); 
  25.  
  26.   if (!open && !active) { 
  27.     return null
  28.   } 
  29.  
  30.   return ReactDOM.createPortal( 
  31.     <div className={'modal ' + aniClassName} onTransitionEnd={onTransitionEnd}> 
  32.       <div className="modal-content">{children}</div> 
  33.       <div className="modal-close-btn" onClick={onClose}> 
  34.         x 
  35.       </div> 
  36.     </div>, 
  37.     document.body, 
  38.   ); 
  39. }; 

动效的流转过程已经实现了,样式也要一起写上。比如我们要实现渐隐渐现的 fade 效果:

  1. .enter { 
  2.   opacity: 0
  3. .enter-active { 
  4.   transition: opacity 200ms ease-in-out; 
  5.   opacity: 1
  6. .enter-done { 
  7.   opacity: 1
  8. .exit { 
  9.   opacity: 1
  10. .exit-active { 
  11.   opacity: 0
  12.   transition: opacity 200ms ease-in-out; 
  13. .exit-done { 
  14.   opacity: 0

如果是要实现放大缩小的 zoom 效果,修改这几个 class 就行。

一个带有动效的弹窗就已经实现了。

使用方式:

  1. const App = () => { 
  2.   const [open, setOpen] = useState(false); 
  3.  
  4.   return ( 
  5.     <div className="app"
  6.       <button onClick={() => setOpen(true)}>show modal</button> 
  7.       <Modal open={open} onClose={() => setOpen(false)}> 
  8.         modal content 
  9.       </Modal> 
  10.     </div> 
  11.   ); 
  12. }; 

类似地,还有 Toast 之类的,也可以这样实现。

[[406650]]

3. react-transition-group

我们在实现动效的思路上借鉴了 react-transition-group 中的 CSSTransition 组件。 CSSTransition 已经帮我封装好了动效展开和关闭的过程,我们在实现弹窗时,可以直接使用该组件。

这里有一个重要的属性: unmountOnExit ,表示在动效结束后,卸载该组件。

  1. const Modal = ({ open, onClose }) => { 
  2.   // http://reactcommunity.org/react-transition-group/css-transition/ 
  3.   // in属性为true/false,true为展开动效,false为关闭动效 
  4.   return createPortal( 
  5.     <CSSTransition in={open} timeout={200} unmountOnExit> 
  6.       <div className="modal"
  7.         <div className="modal-content">{children}</div> 
  8.         <div className="modal-close-btn" onClick={onClose}> 
  9.           x 
  10.         </div> 
  11.       </div> 
  12.     </CSSTransition>, 
  13.     document.body, 
  14.   ); 
  15. }; 

在使用 CSSTransition 组件后,Modal 的动效就方便多了。

4. 总结

至此已把待动效的 React Modal 组件实现出来了。虽然 React 中没有类似 Vue 官方定义的 <transition> 标签,不过我们可以自己或者借助第三方组件来实现。 

 

责任编辑:张燕妮 来源: 蚊子前端博客
相关推荐

2021-03-31 08:01:24

React Portareactcss3

2023-04-28 09:30:40

vuereact

2023-09-05 20:17:18

typescriptPropTypesreact

2021-10-17 20:37:44

组件DrawerReact

2021-04-15 07:50:45

Veu 动效Vue应用程序

2022-03-31 07:46:17

CSS动画技巧

2019-07-24 09:00:19

谷歌Android开发者

2024-03-20 09:31:00

图片懒加载性能优化React

2021-01-28 06:11:40

导航组件Sidenav Javascript

2023-05-17 10:05:35

组件设计(Modal)组件

2023-06-05 15:00:13

书籍翻页动效鸿蒙

2022-06-06 09:28:36

ReactHook

2015-07-31 11:40:36

动效Swift

2023-12-01 11:10:13

CMS开源

2017-03-28 21:03:35

代码React.js

2022-01-17 11:41:50

前端Vite组件

2017-03-23 10:21:57

CSS3动效库前端

2023-07-14 07:23:21

ReactuseEffect

2015-08-03 10:40:45

动效设计优势

2014-09-28 10:39:24

AppleWatchUI
点赞
收藏

51CTO技术栈公众号