scroll-behavior: smooth 必须写在 html 元素上才生效,因浏览器根滚动容器是 html 而非 body;仅对原生锚点链接和未指定 behavior 的 scrollIntoView() 生效,JS 滚动需显式传参。

scroll-behavior: smooth 必须写在 html 元素上才生效,写在 body 或其他选择器里基本没用——这是绝大多数“点了没反应”的真正原因。
为什么只加 html { scroll-behavior: smooth; } 才管用
浏览器的根滚动容器是 html 元素,不是 body。哪怕你看到 body 高度撑满、滚动条在视口右侧,底层滚动上下文仍绑定在 html 上。Safari 尤其严格,body 上的声明会被直接忽略。
- 用开发者工具检查
html元素的 Computed 样式,确认scroll-behavior值确实是smooth,别只信源码 - 某些 CSS 重置(如
* { margin: 0; })或 Normalize.css 会悄悄重置html { overflow: hidden; },导致该属性静默失效 - Next.js、Remix 等框架可能插入 wrapper
div,需用 DevTools 查看实际滚动根是否仍是html
scroll-behavior: smooth 哪些操作能触发,哪些不能
它只对两类行为生效:用户点击 <a href="#section"> 这类原生锚点链接,以及 JS 调用 element.scrollIntoView() 且未传 behavior 参数时的默认行为。
-
element.scrollTop = 100、window.scrollTo(0, 200)—— 完全不受影响,硬跳 - 想用 JS 平滑滚动,必须显式传参:
element.scrollTo({ top: 100, behavior: 'smooth' })或element.scrollIntoView({ behavior: 'smooth' }) - Vue Router / React Router 的
router.push('/page#target')不会触发,得在路由就绪后手动调用scrollIntoView
目标元素被固定导航栏遮住?别算 offset,用 scroll-margin-top
锚点跳转后内容被 position: sticky 或 fixed 导航栏盖住,不是滚动错了,是浏览器默认把目标元素顶部对齐视口顶部。
立即学习“前端免费学习笔记(深入)”;
- 给目标元素加
scroll-margin-top,例如:h2[id] { scroll-margin-top: 60px; }(值建议等于导航栏实际高度) - 响应式场景可用
clamp(50px, 8vh, 80px),比 JS 动态改更轻量 -
scroll-margin-top只对锚点跳转和scrollIntoView()生效,对scrollTo()无效
iOS Safari 对 scroll-behavior: smooth 的支持极不稳定:15.4 之前完全不识别;15.4–16.0 常卡顿或静默降级;16+ 仅对 JS 调用 scrollIntoView({ behavior: 'smooth' }) 有效,CSS 全局声明依然被忽略。移动端关键路径别依赖这行 CSS,它大概率什么都不会做。


















