博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实现一个带有动效的 React 弹窗组件
阅读量:3961 次
发布时间:2019-05-24

本文共 5371 字,大约阅读时间需要 17 分钟。

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

1. 没有动效的弹窗

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

interface ModalProps {  open: boolean;  onClose?: () => void;  children?: any;}const Modal = ({open. onClose, children}: ModalProps) => {  if (!open) {    return null;  }  return createPortal(
{children}
x
, document.body);};

使用方式:

const App = () => {  const [open, setOpen] = useState(false);  return (    
setOpen(false)}> modal content
);};

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

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

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

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

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

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

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

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

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

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

const Modal = ({ open, children, onClose }) => {  const [active, setActive] = useState(false); // 弹窗的存在周期  if (!open && !active) {    return null;  }  return ReactDOM.createPortal(    
{children}
x
, document.body, );};

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

const [aniClassName, setAniClassName] = useState(''); // 动效的class// transition执行完毕的监听函数const onTransitionEnd = () => {  // 当open为rue时,则结束状态为'enter-done'  // 当open未false时,则结束状态为'exit-done'  setAniClassName(open ? 'enter-done' : 'exit-done');  // 若open为false,则动画结束时,弹窗的生命周期结束  if (!open) {    setActive(false);  }};useEffect(() => {  if (open) {    setActive(true);    setAniClassName('enter');    // setTimeout用来切换class,让transition动起来    setTimeout(() => {      setAniClassName('enter-active');    });  } else {    setAniClassName('exit');    setTimeout(() => {      setAniClassName('exit-active');    });  }}, [open]);

Modal 组件完整的代码如下:

const Modal = ({ open, children, onClose }) => {  const [active, setActive] = useState(false); // 弹窗的存在周期  const [aniClassName, setAniClassName] = useState(''); // 动效的class  const onTransitionEnd = () => {    setAniClassName(open ? 'enter-done' : 'exit-done');    if (!open) {      setActive(false);    }  };  useEffect(() => {    if (open) {      setActive(true);      setAniClassName('enter');      setTimeout(() => {        setAniClassName('enter-active');      });    } else {      setAniClassName('exit');      setTimeout(() => {        setAniClassName('exit-active');      });    }  }, [open]);  if (!open && !active) {    return null;  }  return ReactDOM.createPortal(    
{children}
x
, document.body, );};

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

.enter {  opacity: 0;}.enter-active {  transition: opacity 200ms ease-in-out;  opacity: 1;}.enter-done {  opacity: 1;}.exit {  opacity: 1;}.exit-active {  opacity: 0;  transition: opacity 200ms ease-in-out;}.exit-done {  opacity: 0;}

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

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

使用方式:

const App = () => {  const [open, setOpen] = useState(false);  return (    
setOpen(false)}> modal content
);};

自己实现动效的 React 弹窗 demo查看效果。

弹窗的代码:

const Modal = ({ open, children, onClose }) => {  const [active, setActive] = useState(false); // 弹窗的存在周期  const [aniClassName, setAniClassName] = useState(''); // 动效的class  const onTransitionEnd = () => {    setAniClassName(open ? 'enter-done' : 'exit-done');    if (!open) {      setActive(false);    }  };  useEffect(() => {    if (open) {      setActive(true);      setAniClassName('enter');      setTimeout(() => {        setAniClassName('enter-active');      });    } else {      setAniClassName('exit');      setTimeout(() => {        setAniClassName('exit-active');      });    }  }, [open]);  if (!open && !active) {    return null;  }  return ReactDOM.createPortal(    
{children}
x
, document.body, );};

弹窗fade动效的样式:

.fade-enter {  opacity: 0;}.fade-enter-active {  transition: opacity 200ms ease-in-out;  opacity: 1;}.fade-enter-done {  opacity: 1;}.fade-exit {  opacity: 1;}.fade-exit-active {  opacity: 0;  transition: opacity 200ms ease-in-out;}.fade-exit-done {  opacity: 0;}

弹窗zoom动效的样式:

.zoom-enter {  transform: scale(0.3);}.zoom-enter-active {  transition: transform 200ms ease-in-out;  transform: scale(1);}.zoom-enter-done {  transform: scale(1);}.zoom-exit {  transform: scale(1);}.zoom-exit-active {  transform: scale(0);  transition: transform 200ms ease-in-out;}.zoom-exit-done {  transform: scale(0);}

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

3. react-transition-group

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

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

const Modal = ({ open, onClose }) => {  // http://reactcommunity.org/react-transition-group/css-transition/  // in属性为true/false,true为展开动效,false为关闭动效  return createPortal(    
{children}
x
, document.body, );};

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

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

 

4. 总结

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

转载地址:http://vdqzi.baihongyu.com/

你可能感兴趣的文章
使用 Docker 容器应该避免的 10 个事情
查看>>
postgres基本操作(个人总结版)
查看>>
求数组中最长递增子序列
查看>>
用C++设计一个不能被继承的类
查看>>
linux core文件机制
查看>>
私有继承中的派生类对象与基类对象间的转换
查看>>
5.7 观察者模式observer(行为模式)
查看>>
建造者模式Builder(创建模式)
查看>>
Linux文件系统目录结构的详细解说(一)
查看>>
TIME_WAIT状态的意义
查看>>
千万不要把 bool 设计成函数参数
查看>>
linux文件属性及权限详解
查看>>
Find 命令使用详解
查看>>
Ext4,Ext3的特点和区别
查看>>
Linux文件系统目录结构的详细解说(二)
查看>>
Linux umount 报 device is busy 的处理方法
查看>>
一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
查看>>
提供机制而不是策略
查看>>
内核中断机制
查看>>
内核抢占
查看>>