真正可靠的移动端局部平滑滚动需同时满足:容器显式设置height/max-height、overflow-y及scroll-behavior:smooth;iOS≤15.3和微信X5内核不支持;须用JS检测scrollIntoView并指定root;还需适配prefers-reduced-motion。

局部滚动容器必须显式声明 scroll-behavior: smooth
只给 html 加 scroll-behavior: smooth 对局部区域完全无效。移动端的局部滚动(比如聊天列表、商品筛选栏)必须作用在实际滚动的容器元素上,且该容器需同时满足:
• 有明确高度或 max-height
• 设置了 overflow-y: auto 或 scroll
• 直接在其 CSS 规则中声明 scroll-behavior: smooth
常见错误是把样式写在父级或 body 上,或者用 JS 动态插入容器后没补上该样式——此时即使 DOM 已渲染,滚动仍是瞬时的。
移动端 iOS Safari ≤15.3 和微信 X5 内核根本不解析该属性
不是“不生效”,而是浏览器压根不认这行 CSS。iOS Safari 在 15.4 才开始支持,而大量存量设备仍运行 15.3 及更早版本;微信 Android 使用的 X5 内核默认禁用平滑滚动引擎,@supports (scroll-behavior: smooth) 会返回 true,但实际点击或 JS 调用仍瞬时跳转。
因此不能依赖 CSS 声明兜底,必须配合 JS 检测和降级:
• 先检查 typeof Element.prototype.scrollIntoView === 'function'
• 再调用 el.scrollIntoView({ behavior: 'smooth' }) 并监听滚动事件频率判断是否真平滑
• 失败时 fallback 到 scrollTop + requestAnimationFrame 手动插值
立即学习“前端免费学习笔记(深入)”;
scrollIntoView 在局部容器里必须传 root
如果目标元素在自定义滚动区(如 .chat-list { overflow-y: auto; })内,直接调用 targetEl.scrollIntoView({ behavior: 'smooth' }) 会滚整个页面窗口,而不是那个局部容器。
正确做法是显式指定 root:
• 获取容器引用:const listEl = document.querySelector('.chat-list')
• 调用:targetEl.scrollIntoView({ behavior: 'smooth', block: 'start', root: listEl })
• 注意:root 必须是已布局、非 display: none 的滚动上下文,否则行为未定义
系统“减少动画”偏好会让 behavior: 'smooth' 静默退化为 'auto'
用户开启 macOS/iOS 的“减少动画”设置后,Chrome、Firefox、Safari 会直接忽略 behavior: 'smooth' 参数,且不报错、不触发任何回调。你看到的“没动画”不是 bug,是浏览器主动降级。
无法绕过该策略,但可以提前感知:
• 用 window.matchMedia('(prefers-reduced-motion: reduce)').matches 判断
• 若为 true,直接走瞬时滚动逻辑,避免白等动画
• 不要试图用 CSS 强制覆盖,scroll-behavior 同样受此限制
真正可靠的移动端局部平滑滚动,从来不是靠一行 CSS 解决的。它是一组条件判断:容器是否就绪、环境是否支持、系统是否限制、目标是否可见——漏掉任何一个,smooth 就只是个摆设。


















