最稳妥方案是用 position: fixed + transform 配合 css 过渡控制底部抽屉显示隐藏,禁用 display: none;需关闭 scroll-behavior: smooth、防止滚动穿透、监听 visualviewport 修正键盘顶起,并确保 focus() 在 transition 开始后调用。

直接用 position: fixed + transform 配合 CSS 过渡最稳妥,别碰 display: none 切换或第三方库的默认全屏遮罩逻辑——它们在 iOS Safari 上容易触发页面回弹、输入框失焦、滚动穿透等真实线上问题。
为什么不能用 display: none 控制显示/隐藏
因为 display: none 会彻底移除元素的渲染流,导致:
- transition 动画完全失效(CSS 过渡必须作用于可动画属性,且元素需始终在文档流中)
- 键盘收起后 iOS Safari 可能卡在错误 viewport 高度,页面“被顶上去”回不来
- 再次打开时,focus() 输入框经常不唤起键盘(浏览器认为面板还没渲染完成)
- 更隐蔽的是:某些安卓 WebView 里,display 切换会重置 scroll position,用户正在看的内容突然跳走
正确做法是始终保留在 DOM 中,仅用 opacity 和 transform: translateY() 控制视觉状态:
`.sheet {
position: fixed;
bottom: 0;
left: 0;
right: 0;
max-height: 80vh;
opacity: 0;
transform: translateY(100%);
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.sheet.open {
opacity: 1;
transform: translateY(0);
}`
scroll-behavior: smooth 在 sheet 里要关掉
底部 sheet 内部常含长列表或表单,一旦开启全局 scroll-behavior: smooth,iOS Safari 会出现两个致命问题:
- 滚动到内部元素(如 input)时,页面整体被强制向上推,盖住 sheet 头部
- 快速连续点击多个 input,键盘反复收放,触发多次 layout,UI 卡顿明显
解决方案是局部禁用:
`.sheet-content {
scroll-behavior: auto; /* 覆盖全局设置 */
overflow-y: auto;
-webkit-overflow-scrolling: touch; /* 保持 iOS 惯性滚动 */
}`
同时确保 sheet 容器有明确高度(比如 max-height: 80vh),否则 overflow-y: auto 不生效。
如何防止背景页滚动穿透(iOS 最常见坑)
用户滑动 sheet 内容到底部再继续拖,背景页跟着动——这不是 bug,是 iOS 的原生滚动透传行为。仅靠 overflow: hidden 在 body 上无效,因为 Safari 会忽略固定定位元素外的 body 锁定。
真正有效的组合方案:
- 打开 sheet 时,给
body加position: fixed; top: -${window.scrollY}px;,并记录原始scrollY - 关闭时恢复
body为position: static,再window.scrollTo(0, savedScrollY) - sheet 内部容器必须设
overscroll-behavior: contain(兼容 Chrome 63+/Safari 16.4+) - 对老版 Safari(touchmove 阻止事件冒泡:
element.addEventListener('touchmove', e => e.preventDefault(), { passive: false })
注意:不要对整个 body 绑定 touchmove.preventDefault(),否则会干掉所有手势(包括 sheet 内滚动)。
键盘弹出时 sheet 被顶高?用 visualViewport 监听修正
iOS 键盘弹出会压缩视口,但 fixed 元素仍按原始视口定位,导致 sheet 头部被顶出屏幕顶部。单纯监听 resize 不可靠(Android 和 iOS 触发时机不同)。
现代解法是用 visualViewport API:
if ('visualViewport' in window) {
const updateSheetPosition = () => {
const offset = window.visualViewport.height - window.innerHeight;
sheet.style.bottom = `${offset}px`; // 把 sheet 往上托 offset 像素
};
window.visualViewport.addEventListener('resize', updateSheetPosition);
}
这个 API 在 iOS 13+/Chrome 61+ 支持良好;不支持时降级为不做调整——至少不会错位更严重。别试图用 window.innerHeight 差值计算,它在键盘弹出瞬间不可信。
最麻烦的其实是 input 聚焦时机和 sheet 打开顺序:必须等 sheet 元素已 open 且 CSS transition 开始后,再调用 input.focus(),否则 iOS 可能聚焦失败。这个时序差几毫秒都可能出问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











