You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止Material UI ModalUnstyled在showSpinner时点击遮罩层关闭

解决Material UI ModalUnstyled在showSpinner时点击遮罩层关闭的问题

问题场景

使用Material UI的ModalUnstyled组件时,当showSpinner为true,点击遮罩层(backdrop)仍会触发模态框关闭,尽管已经尝试通过条件化onClickClose来阻止:

onClose={!showSpinner ? onClickClose : () => {}}

核心原因

ModalUnstyled会给遮罩层元素默认绑定点击事件,触发传入的onClose函数。即使自定义了Backdrop组件,这个默认的点击事件依然存在;另外,直接传递空函数给onClose时,若外部show状态的受控逻辑处理不当,仍可能出现意外关闭的情况。

解决方案

方案1:在onClose中过滤backdropClick事件

修改onClose的逻辑,仅当点击原因不是backdropClick,或者showSpinner为false时,才执行关闭操作:

return <ModalUnstyled
    aria-labelledby={ariaForId}
    aria-describedby={ariaForId}
    open={show}
    onClose={(event, reason) => {
        // 遮罩层点击且showSpinner为true时,不执行关闭
        if (reason === 'backdropClick' && showSpinner) return;
        onClickClose();
    }}
    className={'modal-container'}
    slots={{backdrop: BackdropUnstyled}}>
    {/* 模态框内容 */}
</ModalUnstyled>

方案2:阻止自定义Backdrop内的事件冒泡

在自定义的Backdrop组件中,给最外层div添加点击事件并阻止冒泡,避免触发ModalUnstyled默认的遮罩层点击事件:

const BackdropUnstyled = React.forwardRef<HTMLDivElement, { open?: boolean; className: string }>((props, ref) => {
    const {ownerState, open, className, ...other} = props;
    
    const handleBackdropClick = (e: React.MouseEvent) => {
        if (showSpinner) {
            e.stopPropagation();
            return;
        }
        onClickClose();
    };

    return <div
        className={clsx({'MuiBackdrop-open': open}, 'modal-backdrop', className)}
        ref={ref}
        onClick={handleBackdropClick}
        {...other}>
        <BlurOverlay onClick={showSpinner ? undefined : onClickClose}/>
    </div>
});

额外优化:同步BlurOverlay的点击逻辑

调整BlurOverlay的onClick传递,确保只有在!showSpinner时才绑定关闭函数,避免不必要的触发:

<BlurOverlay onClick={showSpinner ? undefined : onClickClose}/>

完整修改后的代码示例

export const Modal = ({
                          children,
                          show,
                          onClickClose,
                          showSpinner,
                      }: ModalProps) => {
    const ariaForId = `:modal-id-aria-${guidGenerator()}:`

    const BackdropUnstyled = React.forwardRef<
        HTMLDivElement,
        { open?: boolean; className: string }
    >((props, ref) => {
        // @ts-ignore
        const {ownerState, open, className, ...other} = props;
        
        const handleBackdropClick = (e: React.MouseEvent) => {
            if (showSpinner) {
                e.stopPropagation();
                return;
            }
            onClickClose();
        };

        return <div
            className={clsx({'MuiBackdrop-open': open}, 'modal-backdrop', className)}
            ref={ref}
            onClick={handleBackdropClick}
            {...other}>
            <BlurOverlay onClick={showSpinner ? undefined : onClickClose}/>
        </div>
    });

    return <ModalUnstyled
        aria-labelledby={ariaForId}
        aria-describedby={ariaForId}
        open={show}
        onClose={(event, reason) => {
            if (reason === 'backdropClick' && showSpinner) return;
            onClickClose();
        }}
        className={'modal-container'}
        slots={{backdrop: BackdropUnstyled}}>
        <div role={'dialog'}>
            {showSpinner && <div/>}
            <div>
                {showSpinner && <Spinner/>}
                {children}
            </div>
        </div>
    </ModalUnstyled>
}

interface BlurOverlayProps extends PropsWithOptionalChildren {
    classes?: string | string[],
    onClick?: () => void,
}

export const BlurOverlay = ({
                                children,
                                classes,
                                onClick,
                            }: BlurOverlayProps) => {
    const [domElementForBlur, setDomElementForBlur] = useState<HTMLElement | null>(null)
    useEffect(() => {
        const elementToApplyBlurTo = document.getElementById('modal-container-anchor')
        if (elementToApplyBlurTo) {
            elementToApplyBlurTo.setAttribute('style', 'backdrop-filter: url(#svgBlur); backdrop-filter: blur(5px); width: 100%; height: 100%; position: absolute; top: 0; left: 0')
            setDomElementForBlur(elementToApplyBlurTo)
        }

        return () => {
            if (elementToApplyBlurTo) elementToApplyBlurTo.setAttribute('style', '')
        }
    }, [])

    const _onClick = () => {
        if (domElementForBlur) domElementForBlur.setAttribute('style', '')
        if (onClick) onClick()
    }

    return <div
        className={clsx('blur-overlay-container', classes)}
        onClick={_onClick}>
        {children}
    </div>
}

内容的提问来源于stack exchange,提问作者four-eyes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 01:00:10