popup组件封装

需求

为减少第三方库的非必要引用,选择使用原生封装一个popup组件。

思路

dom元素包括遮罩层和内容区两部分,使用fixed定位确定弹出位置,使用css动画对遮罩层(背景透明度)和内容区(滑动)做动画,通过插槽方式传入内容区显示内容。

效果

在这里插入图片描述

使用方法

import Popup from '@m/components/popup' //引入组件
import { useEffect, useState } from 'react'
import $style from './index.scss'

const Home = () => {
    type Placement = 'top' | 'bottom' | 'left' | 'right' | 'center'

    const [visible, setVisible] = useState(false)
    const [placement, setPlacement] = useState<Placement>('top')

    const open = (pos: Placement) => {
        setVisible(true)
        setPlacement(pos)
    }

    return (
        <div className={$style.test_btn}>
            <button onClick={() => open('top')}>顶部弹出</button>
            <button onClick={() => open('bottom')}>底部弹出</button>
            <button onClick={() => open('left')}>左侧弹出</button>
            <button onClick={() => open('right')}>右侧弹出</button>
            <button onClick={() => open('center')}>中心弹出</button>

//使用组件,placement弹出方位、visible控制打开关闭、closePopup关闭函数、popupClass自定义pupop组件样式、clickMaskClose是否点击遮罩关闭
            <Popup 
				placement={placement}
				visible={visible}
				closePopup={setVisible}
				popupClass={$style.popup_class}
				clickMaskClose={true}
			>
                <div>测试popup</div>
            </Popup>
        </div>
    )
}
export default Home

API

props

在这里插入图片描述

slot

在这里插入图片描述

代码

import { FC, useEffect, useState } from 'react'
import $style from './index.scss'
import { ISpopup } from './type'

const Popup: FC<ISpopup> = ({
    children,
    placement = 'top',
    visible,
    closePopup,
    popupClass,
    clickMaskClose = true
}) => {
    const [isShowPop, setIsShowPop] = useState(false)

    useEffect(() => {
        let timer: number

        if (visible) {
            setIsShowPop(true)
        } else {
            timer = setTimeout(() => {
                setIsShowPop(false)
            }, 500)
        }

        return () => clearTimeout(timer)
    }, [visible])

    // 匹配打开方位 对应动画样式
    const pitchPosClass = () => {
        const posMap = {
            top: [
                $style.popup_content_top,
                visible ? $style.popup_content_top_show : $style.popup_content_top_hidden
            ],
            bottom: [
                $style.popup_content_bottom,
                visible ? $style.popup_content_bottom_show : $style.popup_content_bottom_hidden
            ],
            left: [
                $style.popup_content_left,
                visible ? $style.popup_content_left_show : $style.popup_content_left_hidden
            ],
            right: [
                $style.popup_content_right,
                visible ? $style.popup_content_right_show : $style.popup_content_right_hidden
            ],
            center: [
                $style.popup_content_center,
                visible ? $style.popup_content_center_show : $style.popup_content_center_hidden
            ]
        }
        return posMap[placement]
    }

    const close = () => {
        clickMaskClose && closePopup(false)
    }

    return (
        isShowPop && (
            <div className={$style.popup}>
                <div
                    className={[
                        $style.popup_mask,
                        visible ? $style.popup_mask_show : $style.popup_mask_hidden
                    ]}
                    onClick={close}
                ></div>
                <div className={[popupClass, pitchPosClass()]}>{children}</div>
            </div>
        )
    )
}

export default Popup
.popup {
    // 遮罩
    .popup_mask {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
    }
    .popup_mask_show {
        background-color: rgba(0, 0, 0, 0.4);
        animation: maskshow 0.5s;
    }
    .popup_mask_hidden {
        background-color: transparent;
        animation: maskhidden 0.5s;
    }

    // 顶部
    .popup_content_top {
        position: fixed !important;
        background-color: #fff;
        top: 0 !important;
        left: 0 !important;
        width: 100vw;
        height: 20vh;
        border-radius: 0 0 6px 6px;
    }
    .popup_content_top_show {
        transform: none;
        animation: topshow 0.5s;
    }
    .popup_content_top_hidden {
        transform: translate(0, -100%);
        animation: tophidden 0.5s;
    }

    // 底部
    .popup_content_bottom {
        position: fixed !important;
        background-color: #fff;
        bottom: 0 !important;
        left: 0 !important;
        width: 100vw;
        height: 20vh;
        border-radius: 6px 6px 0 0;
    }
    .popup_content_bottom_show {
        transform: none;
        animation: bottomshow 0.5s;
    }
    .popup_content_bottom_hidden {
        transform: translate(0, 100%);
        animation: bottomhidden 0.5s;
    }

    // 左侧
    .popup_content_left {
        position: fixed !important;
        background-color: #fff;
        top: 0 !important;
        left: 0 !important;
        width: 60vw;
        height: 100vh;
        border-radius: 0 6px 6px 0;
    }
    .popup_content_left_show {
        transform: none;
        animation: leftshow 0.5s;
    }
    .popup_content_left_hidden {
        transform: translate(-100%, 0);
        animation: lefthidden 0.5s;
    }

    // 右侧
    .popup_content_right {
        position: fixed !important; 
        background-color: #fff;
        top: 0 !important;
        right: 0 !important;
        width: 60vw;
        height: 100vh;
        border-radius: 6px 0 0 6px;
    }
    .popup_content_right_show {
        transform: none;
        animation: rightshow 0.5s;
    }

    .popup_content_right_hidden {
        transform: translate(100%, 0);
        animation: righthidden 0.5s;
    }

    // 中心
    .popup_content_center {
        position: fixed !important;
        background-color: #fff;
        top: 50% !important;
        left: 50% !important;
        width: 30vw;
        height: 20vh;
        border-radius: 6px;
    }
    .popup_content_center_show {
        transform: translate(-50%, -50%) scale(1);
        animation: centershow 0.5s;
    }

    .popup_content_center_hidden {
        transform: translate(-50%, -50%) scale(0);
        animation: centerhidden 0.5s;
    }
}

//  动画
// 遮罩动画
@keyframes maskshow {
    0% {
        opacity: 0;
        background-color: transparent;
    }
    100% {
        opacity: 1;
        background-color: rgba(0, 0, 0, 0.4);
    }
}
@keyframes maskhidden {
    0% {
        opacity: 1;
        background-color: rgba(0, 0, 0, 0.4);
    }
    100% {
        opacity: 0;
        background-color: transparent;
    }
}

// 顶部popup动画
@keyframes topshow {
    0% {
        transform: translate(0, -100%);
    }
    100% {
        transform: none;
    }
}
@keyframes tophidden {
    0% {
        transform: none;
    }
    100% {
        transform: translate(0, -100%);
    }
}

// 底部popup动画
@keyframes bottomshow {
    0% {
        transform: translate(0, 100%);
    }
    100% {
        transform: none;
    }
}
@keyframes bottomhidden {
    0% {
        transform: none;
    }
    100% {
        transform: translate(0, 100%);
    }
}

// 左侧popup动画
@keyframes leftshow {
    0% {
        transform: translate(-100%, 0);
    }
    100% {
        transform: none;
    }
}
@keyframes lefthidden {
    0% {
        transform: none;
    }
    100% {
        transform: translate(-100%, 0);
    }
}

// 右侧popup动画
@keyframes rightshow {
    0% {
        transform: translate(100%, 0);
    }
    100% {
        transform: none;
    }
}
@keyframes righthidden {
    0% {
        transform: none;
    }
    100% {
        transform: translate(100%, 0);
    }
}

// 中心popup动画
@keyframes centershow {
    0% {
        transform: translate(-50%, -50%) scale(0);
    }
    100% {
        transform: translate(-50%, -50%) scale(1);
    }
}
@keyframes centerhidden {
    0% {
        transform: translate(-50%, -50%) scale(1);
    }
    100% {
        transform: translate(-50%, -50%) scale(0);
    }
}
好的,我可以帮您封装一个van-popup组件。以下是代码示例: ```html <template> <van-popup v-model="show" :position="position" :style="{width: width}"> <div class="popup-content"> <slot></slot> </div> </van-popup> </template> <script> import { Popup } from 'vant'; export default { components: { [Popup.name]: Popup }, props: { show: { type: Boolean, required: true }, position: { type: String, default: 'center' }, width: { type: String, default: '80%' } } }; </script> <style scoped> .popup-content { padding: 20px; } </style> ``` 使用: ```html <template> <div> <van-button @click="showPopup = true">显示弹出框</van-button> <van-popup-component :show="showPopup" position="bottom" width="90%"> <div>这里是弹出框内容</div> <van-button @click="showPopup = false">关闭</van-button> </van-popup-component> </div> </template> <script> import VanPopupComponent from './VanPopupComponent.vue'; export default { components: { VanPopupComponent }, data() { return { showPopup: false }; } }; </script> ``` 这个组件是基于vant的Popup组件进行封装的,支持传入`show`、`position`、`width`三个参数,其中`show`是必传参数,用于控制弹出框的显示和隐藏。`position`用于控制弹出框的位置,可选值为`top`、`bottom`、`left`、`right`、`center`,默认值为`center`,`width`用于控制弹出框的宽度,默认值为`80%`。在组件内部,使用了`<slot>`来插入弹出框内容,可以方便地自定义弹出框的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值