弹窗显示时禁止页面滚动(兼容iOS移动端,弹窗可滚动)
iOS 移动端弹窗禁止页面滚动+弹窗可滚动的靠谱方案
问题根源
iOS Safari的弹性滚动机制很特殊:当弹窗滚动到顶部/底部时,继续滑动会触发底层页面的滚动。常规的overflow: hidden、第三方框架之所以失效,要么是没针对iOS的弹性滚动做处理,要么是操作body时没处理滚动位置导致黑条。
直接上可运行的方案
第一步:先写好CSS
给弹窗容器加滚动样式,同时给body准备一个锁定类:
/* 弹窗容器:确保自身可滚动,且iOS下顺滑 */ .modal-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; /* 必须加,开启iOS原生顺滑滚动 */ background: rgba(0,0,0,0.5); padding: 20px; box-sizing: border-box; } /* 弹窗内容:避免容器滚动时内容太贴边 */ .modal-content { background: white; padding: 15px; border-radius: 8px; } /* 弹窗打开时给body加的类:固定位置+禁止滚动 */ body.modal-open { position: fixed; width: 100%; overflow: hidden; }
第二步:React 中的实现逻辑(解决黑条+彻底锁滚动)
import { useEffect, useState } from 'react'; const Modal = ({ isOpen, onClose, children }) => { // 存弹窗打开前的滚动位置,用来关闭时恢复 const [prevScrollTop, setPrevScrollTop] = useState(0); useEffect(() => { if (!isOpen) return; // 1. 记录当前滚动位置 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; setPrevScrollTop(scrollTop); // 2. 锁定body滚动:加类+设置top偏移,避免页面跳回顶部 document.body.classList.add('modal-open'); document.body.style.top = `-${scrollTop}px`; // 3. 监听弹窗的touchmove事件,阻止边界滚动时触发页面滚动 const handleModalTouchMove = (e) => { const modal = e.currentTarget; // 判断弹窗是否滚到顶/底 const isTop = modal.scrollTop === 0; const isBottom = modal.scrollHeight - modal.scrollTop === modal.clientHeight; // 如果在顶部还往下滑,或者在底部还往上滑,就阻止默认行为 if ((isTop && e.touches[0].clientY > e.changedTouches[0].clientY) || (isBottom && e.touches[0].clientY < e.changedTouches[0].clientY)) { e.preventDefault(); } }; const modalEl = document.querySelector('.modal-container'); if (modalEl) { // passive: false 必须加,不然preventDefault可能无效 modalEl.addEventListener('touchmove', handleModalTouchMove, { passive: false }); } // 清理函数:关闭弹窗时恢复 return () => { document.body.classList.remove('modal-open'); document.body.style.top = ''; // 滚动回之前的位置,彻底解决黑条问题 window.scrollTo(0, prevScrollTop); if (modalEl) { modalEl.removeEventListener('touchmove', handleModalTouchMove); } }; }, [isOpen]); if (!isOpen) return null; return ( <div className="modal-container"> <div className="modal-content"> {children} <button onClick={onClose} style={{ marginTop: '20px' }}>关闭弹窗</button> </div> </div> ); }; export default Modal;
为啥之前的方法无效?
overflow: hidden:iOS Safari 对body设置这个属性时,弹性滚动触发后还是会带动页面动,根本拦不住。touchAction: none:全局设置会把弹窗自身的滚动也禁了,直接废了弹窗的滚动功能。body-scroll-lock:你没传弹窗元素作为目标(正确用法是disableBodyScroll(modalEl)),所以它不知道要保护哪个元素的滚动,自然无效。- 那个CSS方案:关闭弹窗时没把页面滚回原来的位置,导致页面位置偏移,出现黑条。
内容的提问来源于stack exchange,提问作者János
相关产品推荐
相关产品推荐

