使用弹窗的时候有如果遮罩层滚动,那么位于body层的背景也会一起滚动,如果弹窗弹出时将 body设置为:

document.body.style.overflow = 'hidden'

,有些情况还要设置其 height="100%"position="fixed",但是仅限于PC端,如果在移动端使用该方法则会失效,以下仅测试了Android方式:
使用e.preventDefault()方法可以让滚动不再穿透,但是弹窗内出现滚动也会一并被阻止,因此,需要按照当前滚动元素的scrollTop属性就行设置是否e.preventDefault(),当然在判断前需要e.stopPropagation(),否则冒泡事件到父元素(此处父元素设置了e.preventDefault()将会让弹窗元素依旧不滚动)。样例代码如下:

<ul
    ref={popRef => {
        popRef?.addEventListener('touchstart', (e) => {
            pageStartY.current = e.changedTouches[0].pageY
            console.log("start")
        });
        popRef?.addEventListener('touchmove', (e) => {
            e.stopPropagation()// Must be
            const deltaY = e.changedTouches[0].pageY - pageStartY.current
            // 禁止向上滚动溢出
            if (e.cancelable && deltaY > 0 && (popRef?.scrollTop || 0) <= 0) {
                e.preventDefault()
            }
            // 禁止向下滚动溢出
            if (
                e.cancelable &&
                deltaY < 0 &&
                popRef?.scrollTop + popRef?.clientHeight >= popRef?.scrollHeight
            ) {
                e.preventDefault()
            }
        }, false);
    }}
    className={"popup_show_list_ul"}>
    <li>1秒</li>
    <li>2秒</li>
    <li className={"active"}>3秒</li>
    <li>4秒</li>
    <li>5秒</li>
    <li>6秒</li>
    <li>7秒</li>
</ul>

弹窗代码如下:

import {getUUID} from "@/utils/common";
import React, {ReactNode, useEffect, useLayoutEffect, useRef, useState} from "react";
import ReactDOM from "react-dom";
import styles from './Popup.module.scss'
import Animated from "../Animated/Animated";

export interface IReactPortalProp {
    children: ReactNode;
    wrapperId: string;
    hasMask?: boolean;
}

const ReactPortal = ({children, wrapperId, hasMask}: IReactPortalProp) => {

    const [wrapper, setWrapper] = useState<Element | null>(null);

    useLayoutEffect(() => {
        let element = document.getElementById(wrapperId);

        let created = false;

        if (!element) {
            created = true;
            const wrapper = document.createElement('div');
            wrapper.setAttribute("id", wrapperId);
            document.body.appendChild(wrapper);
            element = wrapper;
            document.body.style.overflow = 'hidden';
        }

        setWrapper(element);

        //will clean up container element when ReactPortal unmounted.
        return () => {
            if (created && element?.parentNode) {
                document.body.style.overflow = 'auto';
                element.parentNode.removeChild(element);
            }
        }
    }, [wrapperId]);

    if (wrapper == null) return null;

    return ReactDOM.createPortal(children, wrapper);
}

export interface IPopupProp {
    children: ReactNode;
    open?: boolean;
    xPos?: number;
    yPos?: number;
    showMask?: boolean;
    hasShadow?: boolean;
    maskCloseable?: boolean;
    borderRadius?: number;
    styleMaskColor?: string; //#00FF00FF || rgba(0,0,0,0)
    onClose?: (e: any) => void;
    callback?: (e: any) => void;
}

const Popup: React.FC<IPopupProp> = (props) => {

    const {open, xPos, yPos, showMask} = props;
    const [isShow, setIsShow] = useState(false);

    const [wrapId, setWrapId] = useState(getUUID);
    const pageStartY = useRef(0);

    const handleMaskClick = (e: any) => {
        e.stopPropagation();
        if (props.onClose && props.maskCloseable) props.onClose(e);
    }

    useEffect(() => {
        // setIsShow(props.open || false)
        document.getElementById(wrapId)?.setAttribute("active", props.open + '');
    }, [props.open])

    return (props.open ? <ReactPortal
        hasMask={props.showMask}
        wrapperId={wrapId}>
        <Animated>
            <div
                style={{backgroundColor: showMask ? props.styleMaskColor : ``}}
                className={styles.container}
                ref={(elem) => {
                    elem?.addEventListener('touchmove', (e) => {
                        e.preventDefault();
                    }, false);
                }}
                onClick={handleMaskClick}
            >
                <div
                    className={`${styles.main}
                    ${props.hasShadow ? styles.has_shadow : ""}`}
                    style={{top: yPos, left: xPos, borderRadius: props.borderRadius}}
                    onClick={(e) => e.stopPropagation()}>
                    {props.children}
                </div>
            </div>
        </Animated>
    </ReactPortal> : null)
}

Popup.defaultProps = {
    maskCloseable: true,
    showMask: false,
    xPos: 0,
    yPos: 0,
    borderRadius: 16,
    styleMaskColor: 'rgba(51, 51, 51, 0.05)'
}

export default Popup;
最后修改:2023 年 11 月 20 日
如果觉得我的文章对你有用,请随意赞赏