使用react-spring封装一个Popup组件

595 阅读1分钟

思路

dom元素包括遮罩层和内容区域这两部分。

都可以使用fixed定位。

动画使用react-spring库。

Popup组件内部维护一个变量控制dispaly属性。

主要逻辑是当弹出层显示动画开始时,将遮罩层的display属性通过内部维护的变量设为block;当弹出层消失动画结束时,将遮罩层的display属性通过内部维护的变量设为none。

具体实现

import type { ReactNode } from 'react'
import { useState } from 'react'
import styled from 'styled-components'
import { animated, useSpring } from '@react-spring/web'

const PopupStyled = styled.div`
position: absolute;
  .mask {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vh;
    height: 100vh;
    background-color: #3e2b6da8;
    z-index: 256;
  }
  .content {
    width: 100%;
    position: fixed;
    bottom: 0;
    background-color: #fff;
    z-index: 512;
  }
`
interface P {
  visible: boolean
  children: ReactNode
  onMaskClick?: () => void
}
export const Popup: React.FC<P> = (p) => {
  const { visible, children, onMaskClick } = p
  const [maskVisible, setMaskVisible] = useState<boolean>(false)
  const contentStyle = useSpring({
    transform: visible ? 'translateY(0%)' : 'translateY(100%)',
  })
  const maskStyle = useSpring({
    opacity: visible ? 1 : 0,
    onStart({ value }) {
      if (value.opacity < 0.1) {
        setMaskVisible(true)
      }
    },
    onRest({ value }) {
      if (value.opacity < 0.1) {
        setMaskVisible(false)
      }
    },
  })
  const hClickMask = () => {
    onMaskClick?.()
  }
  return (
    <PopupStyled id='popup-mask'>
      <animated.div className={'mask'} style={{ ...maskStyle, display: maskVisible ? 'block' : 'none' }} onClick={hClickMask}>
        <animated.div className={'content'} style={{ ...contentStyle }}>
          {children}
        </animated.div>
      </animated.div>
    </PopupStyled>
  )
}

使用方法

外部声明状态visible,并将其作为props传入Popup组件。

可以传入一个回调函数,当点击遮盖层时会调用。

import { useState } from 'react'
import { Popup } from '../../../components/Popup'

export const Playground: React.FC = () => {
  const [visible, setVisible] = useState<boolean>(false)
  return (
    <div>
      <button onClick={() => setVisible(true)}>下方弹出</button>
      <Popup visible={visible} onMaskClick={() => { setVisible(last => !last) }}>
        <div>弹窗内容</div>
      </Popup>
    </div>
  )
}

popup.gif