给落地页做了锚点导航,点「价格」跳到对应区块,结果标题被吸顶的导航栏压住一半。scrollIntoView 一行代码就滚过去了,位置却总是差 60 像素。这类问题今年遇到三次,每次都是同一个原因。

最省事的做法是 CSS

给目标区块加 scroll-margin-top,值设成导航栏高度,比如 64px。浏览器在做锚点跳转和 scrollIntoView 时会自动留出这段距离,JS 一行都不用改。兼容性现在没问题,主流浏览器都支持。老项目里常见的 padding-top 加负 margin 的写法可以删掉了。

用 window.scrollTo 控制细节

需要平滑滚动就直接传行为参数:

window.scrollTo({ top: 800, behavior: 'smooth' })

top 是相对文档顶部的绝对距离,不是相对当前位置。想滚到某个元素,用元素的位置加上当前滚动量算:

const y = el.getBoundingClientRect().top + window.pageYOffset - 64;

减掉的 64 就是导航高度。pageYOffset 不要写成 scrollTop 的文档版本,两者在不同浏览器上有差异。

滚动容器不是 window 时

  • 内容放在 overflow auto 的 div 里,要改的是这个 div 的 scrollTop,window 的滚动量始终是 0。
  • 先确认到底谁在滚。开发者工具里选中容器,看它的 scrollHeight 是否大于 clientHeight。
  • 嵌套滚动区里给外层也加了 smooth,滚动会打架,只在内层设。

几个容易踩的坑

页面 body 上有 overflow hidden 时,scrollTo 完全不生效,控制台也不报错,先检查样式。滚动过程中用户手动滚轮,动画仍在跑,体验很差,可以在 touchstart 或 wheel 事件里中断,或者干脆用原生行为。

老浏览器不支持对象参数,传两个数字的老写法依旧可用,只是没有平滑效果。移动端 Safari 对 smooth 的支持从 15.4 才完善,安卓老机型上偶尔会跳变,接受不了就自己写 requestAnimationFrame 缓动,别用 setInterval 硬推。

选型建议:只有锚点跳转,优先 CSS 方案;需要按条件计算位置,再上 JS。