思路
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>
)
}