touch-action 用于防误判而非修复卡顿,可消除300ms延迟、避免滚动冲突与穿透,但需正确设置位置和值;pan-y 失效主因是容器缺乏纵向滚动能力或被父级覆盖;禁用iOS弹性回弹须配合 passive: false 的 touchmove + preventDefault;遮罩层下页面滚动应精准拦截非可滚动区域;will-change 与 touch-action 滥用反致卡顿,须动态添加并作用于实际位移元素;touch-action 不继承,子元素交互需单独声明。

直接结论:touch-action 不是用来“修”卡顿的,而是用来“防误判”的。它解决不了重绘回流导致的掉帧,但能砍掉 300ms 手势判定延迟、避免滚动冲突、防止滑动穿透——前提是设对位置、设对值。
touch-action: pan-y 为什么加了反而纵向滚不动?
这不是 bug,是浏览器按声明严格执行的结果。当你在某个容器上写了 touch-action: pan-y,浏览器就在 touchstart 阶段锁定了“只响应垂直位移”,后续所有横向偏移都会被忽略;但如果这个容器本身没设置 overflow-y: auto 或内容未溢出,用户划一下根本触发不了原生滚动,手指一松就卡住。
- 必须确保该容器有明确的纵向滚动能力(比如设置了
height+overflow-y: auto) - 父级不能同时设了
touch-action: none或manipulation,否则子元素的pan-y会被覆盖 - 嵌套滚动时(比如弹窗里有个长列表),
pan-y要加在列表容器自身上,而不是弹窗根节点——否则外层截断事件,内层收不到touchmove - Android 某些定制系统(华为 EMUI、小米 HyperOS)对
pan-y响应偏慢,尤其配合transform动画时,建议真机验证
touch-action: none 导致 iOS 弹性回弹还在,怎么彻底禁用?
touch-action: none 确实会禁掉滚动和缩放,但它不碰「弹性滚动」(overscroll bounce)这个物理反馈行为。iOS Safari 就算你把 touch-action: none 写死在 <div class="canvas"> 上,手指拉到顶再松手,页面照样“啪”一下弹回来。
<ul>
<li>真正压住它,得靠 JS:<code>touchmove 事件中调用 e.preventDefault()
{ passive: false },否则 Chrome 和 iOS 14+ 会无视 preventDefault
document 上,只绑在你要锁定的那块区域(比如画布、游戏区)touch-action: none 响应极慢甚至无效,真机测试不可跳过弹窗遮罩层下页面还在滚?别乱加 overflow: hidden
只给 body 加 overflow: hidden 在多数安卓机上无效,真机上照样能拖;而加 position: fixed 虽然能锁住,但会强制 body 回滚到顶部——这是个副作用,不是 bug。
立即学习“前端免费学习笔记(深入)”;
- 推荐做法:监听弹窗根节点的
touchmove,用e.target.closest('.scrollable')判断是否点在可滚动子区域里 - 只有当点击点不在
.scrollable区域内时,才调e.preventDefault() - 这样既阻止了穿透,又保留了弹窗内
textarea、长列表等可滚动区的正常交互 - 千万别对整个遮罩层绑
touchmove并无差别preventDefault,否则弹窗里所有滚动都废了
touch-action 和 will-change 一起用,为什么更卡?
很多人以为加了 touch-action: pan-y 再配个 will-change: transform 就是“双保险”,结果 iOS Safari 卡顿、Android Chrome 掉帧。这是因为 will-change 提前把元素挪进独立图层,反而加重了合成器负担——尤其当页面存在多个该属性或频繁重排时。
-
will-change应只对“正在被 touchmove 驱动的元素”动态添加,滑动结束 100ms 后立刻移除 - 别加在父容器(如
.list-wrapper)上,优先作用于实际位移的子元素(如.list-content) -
touch-action是前提:没有它,will-change单独用几乎无效;浏览器仍会等 300ms 判定手势意图 - 确认该元素没同时用
overflow: hidden+border-radius,这会让浏览器强制回退到软件渲染
最常被忽略的一点:touch-action 不继承,子元素要点击就得单独加 touch-action: manipulation;轮播图里一个按钮点了没反应,大概率不是 JS 问题,而是这个属性被父级的 pan-x 给盖掉了。


















