目 录CONTENT

文章目录

React自定义一个小Modal

Hello!你好!我是村望~!
2023-02-27 / 0 评论 / 0 点赞 / 145 阅读 / 1,286 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

React自定义一个小Modal

涉及到的API和动画库

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。

第二个参数(container)是一个 DOM 元素。

思路

写一个Modal组件 (样式占满全屏~ 背景灰色透明,插槽可插入元素)其实蛮简单的!

然后外部状态控制Modal组件是否显示!(是否挂载到document.body)

使用CSS Transition 控制其动画效果!

首先封装一个Modal组件

// 容器来写样式
const CldModalContainer = styled.div`
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.5);
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #FFFFFF;
`;

// 简单的定义一下组件的props,用于外部传递值控制组件的显示隐藏!
interface ModalProps {
    open: boolean,
    onClose: () => void
}

然后我们的组件就需要 ReactDOM.createPortal 渲染我们的组件,Modal此类组件一般是挂在到 body 的

const CldModal: React.FC<ModalProps> = (props) => {
    const {open, onClose,children} = props
    return ReactDOM.createPortal(
        open ?
            <CldModalContainer onClick={onClose} >
              {children}
            </CldModalContainer>
            : <></>,
        document.body
    )
}
export default CldModal;

上面代码中,open 用来控制modal的显示与隐藏! onClose 用来回调外部组件调用关闭的函数!可以看看下面的使用modal组件

const Layout = ()=>{
    const [openModal,setOpenModal] = useState(false)
    return <LayoutContainer>
        <Content>
            <button onClick={()=>setOpenModal(!openModal)}>显示</button>
            <CldModal open={openModal} onClose={()=>setOpenModal(false)}>
                123123
            </CldModal>
        </Content>
    </LayoutContainer>
}

这样一个基础版本的就完成了,可以看看效果!

Feb-27-2023 16-40-00

开启关闭model增加动画效果

感觉上面的效果比较生硬~ 需要添加一些动画效果~

可以使用 react-transition-group 这个库,来控制组件显示隐藏时候的动画~

我们可以使用 styled 的继承组件效果直接在 CSSTransition 的基础上对内部的动画效果进行自定义

当一个元素从进入页面到退出

进入:(包裹组件第一次挂载的时候,会先执行一次进入的状态改变 appear 相关!)

appear/enter/exit 都有对应 enter(开始进入的样式!),active(进入中的样式!),done(进入结束后的最终样式!)

过程目标动画 (xx-active)和完成动画(xxx-done)基本上都会写成相同的~ 除非可能会有那种反复横跳的效果!

const ModalCSSTransition = styled(CSSTransition)`
  transition: all ${prop=>prop.timeout}ms ease; // 保证过渡时间和下面组件指定的timeout一样
  &.appear,
  &.enter {
    opacity: 0;
  }

  &.appear-active,
  &.appear-done {
    opacity: 1;
  }

  &.enter-done,
  &.enter-active {
    opacity: 1;
  }

  &.exit-done,
  &.exit-active {
    opacity: 0;
  }
`

那么上面的样式基本上就是组件从透明,到显示成最终样式的代码!

然后用这个 ModalCSSTransition 包裹住我们的 Modal组件

这里还需要传一些参数 in 为true的时候,动画状态就会开始变化,timeout表示动画的持续时间! appear 表示元素第一次进入的时候也需要添加动画

更为详细的使用可以参考官方文档~

return ReactDOM.createPortal(
    open ?
        <ModalCSSTransition onClick={onClose} in={open} timeout={300} appear={true}>
        <CldModalContainer onClick={onClose} >
            {children}
        </CldModalContainer>
        </ModalCSSTransition>
        : <></>,
    document.body
)

我们来看看效果!

Feb-27-2023 16-54-47

可以看到我们打开Modal的时候样式是有过渡效果的,但是关闭的时候是直接关闭的,这是为什么呢?可以思考一下🤔!

过渡动画关闭的优化

其实原因就是我们 ReactDOM.createPortal 上面挂载到body的逻辑有问题!

因为我们使用open同时控制了动画效果和DOM的挂载!当open为false的时候!我们的dom已经从body上干掉了!

那我们的过渡效果肯定也就看不到了!

那么解决方式其实也很简单!我们可以内部维护一个和 open 同步的参数 mountOpen 用来控制我们dom的挂载!

open 就专门用来控制动画效果!

当我们的modal从页面上关闭的时候,我们可以让这个mountOpen延迟变为false,延迟的时间的就和我们指定的动画时间一样!

当动画执行结束,我们在让modal组件从body中移除

代码如下:

const CldModal: React.FC<ModalProps> = (props) => {
    const {open, onClose, children} = props
    const [mountOpen, setMountOpen] = useState(open) // 用于控制mount
    const timeOut = 1000;
    useEffect(() => {
        let timer: NodeJS.Timeout | null;
        if (open) {
            setMountOpen(open)
        } else {
          	// 关闭的时候使用定时器延迟关闭!
            timer = setTimeout(() => {
                setMountOpen(false)
                if (timer) {
                    clearTimeout(timer)
                    timer = null;
                }
            }, timeOut)
        }
    }, [open])
    return ReactDOM.createPortal(
        mountOpen ?
            <ModalCSSTransition onClick={onClose} in={open} timeout={timeOut} appear={true}>
                <CldModalContainer onClick={onClose}>
                    {children}
                </CldModalContainer>
            </ModalCSSTransition>
            : <></>,
        document.body
    )
}

Feb-27-2023 17-11-49

这样呢~ 这个modal组件的体验就比较好了!

0

评论区