删掉已废弃的-webkit-overflow-scrolling: touch是第一步,因其在ios 15.4+/safari 16+中引发fixed错位、下拉刷新失灵和掉帧;现代浏览器主动忽略该属性,反而唤醒旧滚动线程导致性能恶化。

加 -webkit-overflow-scrolling: touch 不但不能解决卡顿,反而会让 iOS 15.4+/Safari 16+ 上的 fixed 错位、下拉刷新失灵、掉帧更严重——这个属性已废弃,不是“没加才卡”,是“加了才卡”。
删掉 -webkit-overflow-scrolling: touch 才是第一步
它在现代浏览器里早已不是“开关”,而是干扰项:Chrome 63+ 和 Safari 15.4+ 会主动忽略或降级处理,唤醒旧滚动线程,导致惯性丢失、touchmove 同步阻塞主线程。DevTools Elements 面板搜一下,只要存在就删;还要检查 JS 是否动态注入(比如某些 UI 库初始化时偷偷加上)。
- 别信“加了就有惯性”的老教程——iOS 17 仍可能回退到旧引擎,但代价是合成层错乱和事件链中断
- 安卓 WebView 完全不识别该属性,加了纯属冗余
- 如果删掉后滚动变“硬”(一松手立刻停),问题不在这里,而在下面两点
滚动容器必须有确定高度 + 合成层提示
iOS Safari 只有在明确知道“可滚动范围”时,才会激活原生滚动上下文和惯性。靠 height: auto、flex 内容撑开、或 100vh(地址栏展开时塌缩),都会让容器失效。
- 给滚动容器设
height、max-height或100dvh(iOS 16.4+ 支持) - 确保父级有确定高度,例如:
html, body { height: 100% };Flex 容器中父项需加min-height: 0 - 用
will-change: scroll-position提示 iOS 15+ 创建合成层(旧版无效,别滥用) - 移除所有干扰合成层的祖先样式:
filter、opacity: 0.99、backdrop-filter、mask
touchmove 和 scroll 事件必须带 { passive: true }
这是最常被跳过、效果却最立竿见影的一环。不设 { passive: true },浏览器必须暂停滚动等待 JS 回调执行完毕,iOS 上帧率可能从 60fps 直接掉到 20fps。
- 错误写法:
el.addEventListener('touchmove', handler) - 正确写法:
el.addEventListener('touchmove', handler, { passive: true }) - 同理适用于
scroll:el.addEventListener('scroll', handler, { passive: true }) - 需要阻止默认行为(如拖拽排序)?改用
touchstart+e.preventDefault(),别在scroll里调
别乱用 will-change: transform 或 translateZ(0)
对滚动容器本身加 transform: translateZ(0) 在 iOS 12–15 中可能有用,但在 iOS 15+ 已非必需;而给每个列表项(如 .item)加 will-change: transform 是典型翻车操作——一屏 10 个,滚动中常驻 20+ 图层,中低端安卓机显存吃紧,帧率直接跌破 20fps。
- 只对真实承载位移的容器设
will-change,且必须配真实变化(如scroll-position) - 绝对禁止写
.item { will-change: transform; }这类全局规则 - 用 Chrome DevTools → Layers 面板确认图层数:稳定在 3~5 层为佳,超过 8 层立刻排查
真正卡顿的根源往往不在渲染层,而在主线程被拖住或布局反复重算;will-change 是修水管,虚拟滚动才是换管道——但如果你只是想让一个固定高度的弹窗列表滑得顺,上面四点做完,基本就稳了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











