使用弹窗的时候有如果遮罩层滚动,那么位于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;