scroll-behavior: smooth 仅对锚点跳转和 scrollintoview() 生效,需设在 html 上,js 滚动仍需显式传 behavior: 'smooth',不兼容旧 safari,无回调机制,hash 更新早于滚动完成。

scroll-behavior: smooth 只对 scrollIntoView() 和锚点跳转生效
它不是万能滚动开关,不会让 scrollTop 赋值、window.scrollTo() 或第三方库的滚动自动变平滑。浏览器只在两种场景下尊重这个 CSS 属性:一是用户点击页面内 <a href="#id"></a> 锚点链接;二是 JS 调用元素的 scrollIntoView({ behavior: 'smooth' })(此时 CSS 属性可作为兜底,但优先级低于 JS 参数)。
常见错误现象:scroll-behavior: smooth 写了却没反应——大概率是因为你正用 element.scrollTop = 100 手动滚动,或者用了不带 behavior 的 scrollIntoView()。
- 必须作用于根滚动容器,通常写在
html或body上(推荐html { scroll-behavior: smooth; }) -
body上设置可能被某些重置样式覆盖,且部分浏览器(如旧版 Safari)对body的支持不稳定 - 如果页面有自定义滚动容器(比如
overflow: auto的 div),需单独给该容器设scroll-behavior: smooth
JS 滚动控制必须显式传 { behavior: 'smooth' }
哪怕 CSS 已设全局 scroll-behavior: smooth,JS 的 scrollTo()、scrollBy()、scrollIntoView() 默认仍是瞬时滚动。这是设计使然:JS API 的行为由参数决定,CSS 属于声明式兜底,不覆盖 JS 显式意图。
使用场景:做导航菜单点击滚动、表单错误定位、分页滚动到底部等需要精确控制时机的交互。
立即学习“前端免费学习笔记(深入)”;
-
element.scrollIntoView({ behavior: 'smooth', block: 'start' });—— 推荐始终带block或inline,否则不同浏览器默认对齐方式不一致 -
window.scrollTo({ top: 200, behavior: 'smooth' });—— 注意不要写成window.scrollTo(0, 200),老写法不触发平滑 - 想兼容不支持
behavior: 'smooth'的浏览器?得自己实现简易缓动,或用if ('scrollBehavior' in document.documentElement.style)检测后降级
性能与兼容性:Safari 15.4+ 才真正稳定支持
macOS Monterey 之前的 Safari(包括 iOS 15.2 及更早)对 scroll-behavior: smooth 有明显卡顿、跳帧甚至失效问题;部分安卓 WebView 也存在类似表现。这不是代码写错,是渲染管线未优化到位。
参数差异:scroll-behavior 只接受 auto 或 smooth,没有 fast、slow 等中间档位,也不支持贝塞尔曲线自定义——真要精细控制,只能上 JS。
- 开启平滑滚动会轻微增加主线程压力,长列表快速连续滚动时(比如滚动加载场景),可能比
auto多消耗 1–2ms 渲染时间 - CSS 方式无回调机制,无法监听“滚动结束”,而 JS 的
scrollIntoView()仍不提供 Promise 或事件,需靠setTimeout+getBoundingClientRect()间接判断 - 若同时用 IntersectionObserver 监听滚动位置,注意平滑滚动期间
isIntersecting可能短暂为 false,别误判为离开视口
容易忽略的细节:锚点跳转时 URL hash 变化时机
启用 scroll-behavior: smooth 后,点击锚点链接,URL 中的 #id 会在滚动开始前就更新,而不是等滚动结束。这和 JS scrollIntoView() 行为一致,但和手动 scrollTop 不同——后者完全由脚本控制,hash 可延迟改。
这意味着:如果你依赖 hashchange 事件做路由或状态同步,事件触发时滚动可能还没结束,getBoundingClientRect().top 还没到目标位置。
- 别在
hashchange回调里立刻读取元素位置做计算,加个requestAnimationFrame延迟一帧更稳妥 - 服务端渲染(SSR)页面首次加载带 hash,浏览器会直接滚动,但此时 JS 可能未执行完,相关监听器还没挂载,导致错过初始滚动事件
- Vue/React 单页应用中,用
useEffect或mounted监听 hash 变化时,注意框架路由可能已拦截 hash,需检查是否走的是自己的 history 模式









