878 words
4 minutes
修复首次页面切换时的横向偏移

现象#

站点在新启动后,第一次从首页点击 AboutArchive 时,页面会整体向左偏移一点。第一次触发之后,后续再点同类页面就不会再抖。

这个问题的特征很明确:

  1. 不是每次切页都发生
  2. 只在首次导航时出现
  3. 视觉上像是页面宽度瞬间变了

第一反应#

这种“横向偏移一下”的问题,第一反应通常是滚动条宽度变化:

  • 当前页有滚动条,目标页没有滚动条
  • 或者某个脚本在首次导航时才真正接管滚动容器

所以排查从这两类地方开始:

  • 全局滚动条占位
  • Swup 切页逻辑
  • OverlayScrollbars 是否接管了 body

实际原因#

这次不是单点问题,而是几个因素叠在一起:

1. 页面级滚动条在首次导航时切换状态#

原来布局里对整页启用了 OverlayScrollbars(body)。这类库如果接管的是整个页面,而不是局部容器,就很容易在首次真正初始化时替换原生滚动条,从而导致页面宽度发生一次变化。

这正好解释了为什么:

  • 第一次点 About / Archive 会偏移
  • 之后就稳定了

因为“接管动作”只发生一次。

2. 切页时还有一个临时的页面增高补丁#

布局里原本还有一个 page-height-extend 元素,在 visit:startvisit:end 期间会把页面临时拉高,目的是防止切页滚动动画跳动。

这个补丁会影响页面在切换期间的滚动状态,也会放大滚动条相关的抖动问题。

3. 一部分全局样式是在运行时脚本里导入#

OverlayScrollbarsPhotoSwipe 的样式原来是在 <script> 里导入的。开发模式下,首次导航时样式可能刚好才完成注入,于是页面首跳更容易出现一次性布局抖动。

最终修复#

最后用了几步一起收口:

固定根滚动条占位#

在根布局里加上:

html {
overflow-y: scroll;
scrollbar-gutter: stable;
}

这样即使页面内容高度变化,滚动条槽位也不会突然出现或消失。

不再让 OverlayScrollbars 接管整个 body#

保留局部滚动区域的自定义滚动条,但去掉页面级 OverlayScrollbars(body)

这是这次修复里最关键的一步。

去掉切页期间临时增高页面的补丁#

删除 page-height-extend 和对应的 Swup 钩子逻辑,避免切页过程中人为改变页面滚动状态。

把全局样式提前到 Astro 前置导入#

把:

  • overlayscrollbars/overlayscrollbars.css
  • photoswipe/style.css

从运行时脚本里移到布局文件的前置导入区,让它们在页面首屏就已经是稳定状态。

这次改动的结论#

这种“只在第一次切页时偏一下”的问题,通常不要只盯着某一段动画代码。

更高概率的根因是:

  • 页面级滚动容器被首次初始化
  • 滚动条是否占位发生变化
  • 全局样式在首次导航时才真正生效

如果项目里同时用了页面过渡、滚动条美化、运行时样式注入,这类问题尤其容易出现。

最后验证#

修完后重新执行:

Terminal window
pnpm check
pnpm build

然后重启开发服务器,从首页第一次点击 AboutArchive 验证。

这次在完成上述调整之后,首次导航的横向偏移已经消失。

修复首次页面切换时的横向偏移
https://fuwari.vercel.app/posts/fix-first-navigation-layout-shift/
Author
microcosm
Published at
2026-04-11
License
CC BY-NC-SA 4.0