window.scrollTo 的 { behavior: 'smooth' } 在现代浏览器中已稳定支持,但 Safari 15.2–15.3 存在静默降级 bug;需结合 CSS.supports 检测、requestAnimationFrame 手动插值及 scrollIntoView 备选方案实现真正可靠平滑滚动。

smooth 选项在 window.scrollTo 中是否真的可用
直接说结论:window.scrollTo 的 { behavior: 'smooth' } 在现代浏览器中已稳定支持(Chrome 61+、Firefox 36+、Safari 15.4+、Edge 79+),但 Safari 15.2–15.3 存在兼容性 bug:对 scrollTo({ top, behavior: 'smooth' }) 会静默降级为 instant,不报错也不平滑。别被 DevTools 控制台没报错就骗过去。
验证方式很简单,在 Safari 15.3 下执行:
window.scrollTo({ top: 100, behavior: 'smooth' });如果页面瞬间跳到位置,说明当前环境不生效——这时候得 fallback。
如何写一个真正可靠的平滑滚动跳转函数
不能只靠 behavior: 'smooth' 一招鲜。真实单页跳转常涉及锚点定位、动态内容加载后滚动、以及 Safari 兜底。核心是:先尝试原生 smooth,失败则用 requestAnimationFrame 手动插值。
- 优先检测
CSS.supports('scroll-behavior', 'smooth'),比查 UA 更可靠(但注意它只反映 CSS 层支持,不保证 JS API 行为一致) - 对
id锚点跳转,别直接element.offsetTop—— 如果元素在 sticky header 下方,要减去 header 高度,否则会遮挡 - 用
getBoundingClientRect().top + window.scrollY替代offsetTop,更鲁棒 - 手动实现时,避免用
setTimeout做定时器,用requestAnimationFrame保证帧率
单页路由中触发 scroll 的时机很关键
Vue Router 或 React Router 的 useEffect/onMounted 钩子触发 window.scrollTo 时,DOM 可能还没更新完毕。常见症状:滚动到旧位置、或根本没滚动。
解决方法取决于场景:
- 如果是 hash 跳转(如
#contact),监听hashchange事件,然后setTimeout(() => { /* scrollTo */ }, 0)把任务推到 microtask 后 - 如果是路由变更后渲染新组件,确保在
ref或useRef拿到目标元素后再滚动;React 中可加useLayoutEffect,Vue 中用nextTick - 服务端渲染(SSR)应用要注意:首次 hydration 后不要立即 scroll,等
document.readyState === 'complete'再执行
scrollIntoView 有时比 scrollTo 更省事
如果你只是想让某个元素“进视野”,而不是精确滚动到某像素值,element.scrollIntoView({ behavior: 'smooth', block: 'start' }) 是更简洁的选择,且兼容性略好(Safari 15.4+ 才完全修复 scrollTo smooth,但 scrollIntoView 在 15.2 就基本可用)。
但注意两个坑:
-
scrollIntoView的block默认是'center',容易把目标元素滚到视口中间,挡住顶部导航栏;改成'start'更符合单页锚点习惯 - 若目标元素是
display: none或visibility: hidden,调用会静默失败,需确保元素已渲染且可见 - 它不支持跨 iframe 滚动,而
window.scrollTo可以操作其他 window 对象(比如嵌入的微前端子应用)
真正难的不是写出平滑滚动,而是判断什么时候该用 scrollTo、什么时候该用 scrollIntoView、什么时候必须手写 easing。浏览器差异、布局变化、SSR hydration 时机,三者叠加起来,一个看似简单的 smooth 就很容易漏掉一两个条件。

















