抽屉式导航栏必须用 transform: translatex() + transition: transform 实现滑出收起动画,禁用 left 或 width 过渡,因其触发重排、兼容性差且导致 ios 和低端安卓机卡顿。

抽屉式导航栏滑出收起不丝滑,八成是用了 left 或 width 做过渡,触发了重排;正确做法必须用 transform: translateX() + transition: transform,否则 iOS Safari 和低端安卓机上一卡一卡的。
为什么不能用 left 或 width 做抽屉动画
left 改变会强制浏览器重新计算布局(reflow),width 缩放还会连带影响子元素盒模型、行高、换行等,动画帧率直接掉到 30fps 以下。尤其当抽屉里有图标、文字、子菜单时,卡顿肉眼可见。
- 必须给抽屉容器设
position: relative或absolute,left才生效——但即便生效,也卡 -
width: 0收起时,如果内部有padding或border,会塌缩错位;还得额外处理overflow: hidden和visibility -
transition: width 0.3s在 Safari 上对 flex 子项兼容性差,有时压根不触发
transform: translateX() 的写法和关键约束
抽屉容器初始状态必须是 transform: translateX(-100%)(或具体像素值,如 -300px),展开时改为 translateX(0);只过渡 transform,其他属性别塞进同一个 transition 声明里。
- 父容器必须设
overflow: hidden,否则抽屉滑出时内容溢出可视区 - 不要用
%做translateX的基准——它按自身宽度算,而抽屉宽度可能动态变化;推荐用固定像素(如translateX(-320px))或vw单位 - 加
will-change: transform可提前告知浏览器该元素将动画,避免首帧掉帧(但别滥用,每个元素都加会增加内存开销)
遮罩层(overlay)的 pointer-events 必须动态控制
遮罩不是“打开就 pointer-events: auto,关闭就 none”这么简单。关行动画还在跑,遮罩却已不可点,用户点一下没反应,再点才触发——这是典型的状态不同步。
- 抽屉开始展开时,立刻设遮罩
pointer-events: auto - 抽屉开始收起时,保持
pointer-events: auto,直到transitionend事件触发后,再设pointer-events: none - 千万别用
opacity: 0判断是否隐藏——它不影响点击穿透,pointer-events才是开关
移动端拖拽关闭必须配 preventDefault() 和 passive: false
iOS Safari 默认把 touchmove 当作页面滚动,不加干预,手指一拖,整个页面跟着动,抽屉纹丝不动。
-
touchstart里必须调用event.preventDefault() -
touchmove监听器注册时传{ passive: false },否则浏览器忽略preventDefault() - 位移计算用
event.touches[0].clientX,不用pageX(缩放时不准) - 松手后判断拖动距离是否 > 50px,决定是回弹还是直接关闭;别用
setTimeout模拟,要用requestAnimationFrame做流畅回弹
最易被忽略的一点:抽屉容器的 transform 动画和内容区的联动(比如主区域 margin-left 跟着变)不能靠 class 切换后立刻读 DOM 尺寸——React/Vue 中得等 requestAnimationFrame 或 $nextTick,否则拿到的是旧宽高,动画起始位置偏移。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











