scroll-behavior: smooth 必须作用于 html 元素才生效,仅对原生锚点跳转和 scrollintoview() 生效,受目标可见性、滚动上下文及系统动画偏好影响。

直接写 html { scroll-behavior: smooth; } 就能启用全局平滑滚动,但绝大多数“没动画”问题,根源不在代码本身,而在滚动上下文、目标可见性或浏览器限制。
scroll-behavior 必须作用于 html 元素才生效
写在 body 上完全无效——浏览器真正的滚动根容器是 html 元素,不是 body。哪怕你在 DevTools 里看到 body 样式写了该属性,也不代表它起作用;必须检查 html 的 Computed Styles 中 scroll-behavior 值是否为 smooth。
- 不要套在媒体查询、嵌套选择器或
!important里,它需要作为最终计算样式存在 - 某些 CSS 重置(如
* { margin: 0; })或 Normalize.css 版本会把html的overflow设为hidden,直接废掉滚动上下文 - Next.js、Remix 等框架可能插入 wrapper
div,导致实际滚动根节点不再是html,可用document.documentElement检查
scroll-behavior: smooth 只对两类操作生效
它不接管任何 JS 直接赋值的滚动行为,只响应:
- 用户点击原生锚点链接:
@#@#@#@#@#@#@#@#@#@0 - JS 调用
element.scrollIntoView()且未传参(或显式传{ behavior: 'smooth' })
以下操作完全不受影响,不会自动变平滑:
element.scrollTop = 100window.scrollTo(0, 200)- Vue Router / React Router 的路由跳转(
history.pushState不触发该 CSS)
若需 JS 控制,必须显式加参数:element.scrollIntoView({ behavior: 'smooth' }) 或 window.scrollTo({ top: 100, behavior: 'smooth' })。
锚点跳转后目标被固定导航栏遮挡
这不是滚动错了,而是浏览器默认将目标元素顶部对齐视口顶部,而 position: fixed 或 sticky 导航栏盖住了内容。最稳定解法是用 CSS,而非 JS 计算偏移量:
- 给目标元素加
scroll-margin-top,值等于导航栏高度:<h2 id="section2" style="scroll-margin-top: 64px;">标题</h2> - 若导航栏高度响应式变化(如折叠菜单),建议用 JS 动态更新该值,比反复调用
getBoundingClientRect()更可靠 -
scroll-margin-top只对锚点跳转和scrollIntoView()生效,对scrollTo()无效
目标元素不可见或不在默认文档流中会导致静默失效
即使 CSS 写对了,scroll-behavior 也只在目标可滚动、存在且可见时才触发。典型失效场景包括:
- 目标元素是
display: none、visibility: hidden,或父级有overflow: hidden - 目标被
transform、filter或perspective包裹,创建新层叠上下文(Safari 尤其敏感) - 目标用了
position: absolute脱离文档流,或被包裹在局部滚动容器(如overflow: auto的div)里 - 动态渲染框架(React/Vue)中,id 元素尚未挂载就点击链接,
getElementById返回null,调用静默失败
系统开启「减少动画」偏好(macOS/Windows 设置)时,该属性会被浏览器静默禁用,不报错也不提示——这是设计使然,不是 bug。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











