点击按钮后弹出遮罩层+底部滑入内容,本质是固定定位+css动画+交互控制,需用position: fixed定位弹窗容器并设z-index高于其他元素,遮罩层覆盖全屏且加touch-action: none,弹窗初始transform: translatey(100%),通过class切换至translatey(0)实现滑入,禁用display: none/block以免中断transition动画。

点击按钮后弹出遮罩层+底部滑入内容
移动端底部弹窗本质是「固定定位 + CSS 动画 + 交互控制」,不是靠浏览器原生 API。HTML <button></button> 只负责触发,真正弹出靠 JS 控制元素的 class 切换和 CSS 的 transform 与 transition 配合。
关键点:必须用 position: fixed 定位弹窗容器,且 z-index 要高于页面其他内容;遮罩层(.mask)需覆盖全屏并阻止底层滚动;底部弹窗本身初始设为 transform: translateY(100%),再通过 class 切换恢复到 translateY(0) 实现滑入。
- 遮罩层要加
touch-action: none,避免 iOS 上滑动穿透 - 弹窗容器需设
overscroll-behavior: contain,防止 iOS 滑到底部时触发页面回弹 - 不要用
display: none/block控制显隐——会中断 transition 动画
HTML 结构怎么写才不踩坑
结构必须满足语义清晰、可访问、易控制三个要求。常见错误是把弹窗写在按钮后面但没做 aria-hidden 同步,或遮罩层缺失 role="dialog" 导致屏幕阅读器识别异常。
推荐结构如下(注意属性顺序和必需值):
<button id="openBtn" aria-expanded="false" aria-controls="bottomSheet">打开弹窗</button> <p></p><div id="bottomSheet" class="bottom-sheet" role="dialog" aria-modal="true" aria-labelledby="sheetTitle"> <div class="mask" tabindex="-1" aria-hidden="true"></div> <div class="sheet-content"> <h3 id="sheetTitle">操作选项</h3> <p>这里是内容</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher"><img src="https://img.php.cn/upload/skill/000/000/081/179109368394970.jpg" alt="Wechat HTML Publisher" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="overflowclass">Wechat HTML Publisher</a> <p class="overflowclass">直接上传HTML富文本到微信公众号草稿箱。支持完整的HTML格式,无需Markdown转换。</p> </div> <a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div> </div> </div>
-
aria-expanded必须和按钮状态同步,JS 中每次点击都要手动更新 -
tabindex="-1"加在遮罩层上,确保键盘焦点能被捕获,避免跳到背景内容 - 不要把
<button></button>放进弹窗内部再用click关闭——容易触发多次事件冒泡
JavaScript 怎么控制开关和焦点逻辑
核心是两件事:开弹窗时锁 body 滚动、关弹窗时恢复焦点;同时确保键盘用户能用 Esc 关闭,且 Tab 键只在弹窗内循环。
关键代码逻辑(不依赖框架):
const btn = document.getElementById('openBtn');
const sheet = document.getElementById('bottomSheet');
const mask = sheet.querySelector('.mask');
<p>function openSheet() {
document.body.style.overflow = 'hidden';
sheet.classList.add('active');
btn.setAttribute('aria-expanded', 'true');
sheet.querySelector('.sheet-content').focus();
}</p><p>function closeSheet() {
document.body.style.overflow = '';
sheet.classList.remove('active');
btn.setAttribute('aria-expanded', 'false');
btn.focus();
}</p><p>mask.addEventListener('click', closeSheet);
sheet.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSheet();
});</p><p>// 焦点循环(简化版)
sheet.addEventListener('keydown', (e) => {
const focusable = sheet.querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
const first = focusable[0];
const last = focusable[focusable.length - 1];</p><p>if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});</p>
- 别用
body { position: fixed }锁滚动——iOS Safari 会重置 scrollTop,导致页面跳回顶部 -
focus()必须在class切换后调用(或加setTimeout),否则可能聚焦失败 - 如果弹窗里有
<input>,首次打开后应自动focus()到它,而非 content 容器
为什么 iOS 上弹窗偶尔卡住不动
大概率是 CSS 动画未触发硬件加速,或 transition 属性被意外覆盖。iOS WebKit 对 transform 和 opacity 的动画支持最稳定,其他属性(如 height、top)容易掉帧甚至卡死。
检查以下三点:
- 弹窗的
transition必须只含transform和opacity,例如:transition: transform 0.3s cubic-bezier(0.33, 1, 0.68, 1), opacity 0.2s - 给弹窗容器加
will-change: transform(仅在 active 状态下),避免首帧卡顿 - 确保没有其他 CSS 规则(比如父级的
overflow: hidden或transform: scale())干扰定位计算
真机调试时,优先用 Safari 的「开发者 → 检查元素 → 3D 图层」看是否触发了合成层——没触发就说明动画没走 GPU。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










