跳到主内容
Zhimalab

修复页面两边留白与滚动条偏移问题

2026-07-12

问题描述

本地 pnpm dev 运行页面浑然一体,如 http://localhost:4322/tutorial/math-map。但部署到线上后出现两个问题:

  1. 页面两侧有留白
  2. 滚动条不在浏览器窗口最右侧靠边的位置

线上地址:http://115.191.0.179/tutorial/math-map/

布局结构回顾

页面使用 AppLayout.astro 布局,结构如下:

body (overflow-hidden)
└── div.flex.h-screen.w-full
      ├── aside (sidebar, 60px)
      └── main.flex-1
            ├── header (顶部导航)
            └── div.overflow-y-auto  ← 滚动发生在这里
                  └── 页面内容

根因分析

根因 1:body { overflow: hidden }

AppLayout.astro 第 60 行给 body 设置了 overflow-hidden,导致滚动被委派给内层 <div class="overflow-y-auto">

后果:

  • 滚动条出现在 main 内容区内部,而非视口最右边
  • macOS 的叠加滚动条(overlay scrollbar)下不易察觉,但在始终显示滚动条的系统上非常明显

根因 2:flex 容器 h-screen 约束

外层 flex 容器使用 h-screen(100vh),这意味着整个布局被锁死在视口高度内。配合 body 的 overflow-hidden,形成了一种“app shell”式布局:body 永远不滚动,滚动完全由内层 div 承担。

根因 3:button, a { overflow: hidden } 全局样式

globals.css 中存在一段用于按钮波纹效果的全局 CSS:

button, a {
  position: relative;
  overflow: hidden;
}

这个选择器范围太宽,对所有 <a><button> 元素生效。虽然它对侧边栏导航链接的直接影响不大,但在生产构建中 CSS 合并后的优先级可能导致意外的布局干扰。

修复方案

修改 1:移除 body 的 overflow-hidden

- class="... overflow-hidden selection:bg-blue-500/30"
+ class="... selection:bg-blue-500/30"

让 body 恢复正常滚动行为,滚动条随之出现在视口最右侧。

修改 2:sidebar 改用 position: fixed

原来 sidebar 是 flex 子元素,配合 sticky 定位在 flex 容器内表现不稳定(不同浏览器行为不一致)。改用 fixed 定位:

- class="hidden md:flex w-[60px] flex-col ... sticky top-0 h-screen z-20"
+ class="hidden md:flex w-[60px] flex-col fixed left-0 top-0 h-screen ... z-20"

修改 3:flex 容器改为 min-h-screen,main 添加 sidebar 偏移

- <div class="flex h-screen w-full">
+ <!-- 移除外层 flex 容器,sidebar 和 main 直接作为 body 子元素 -->

- <main class="flex-1 flex flex-col h-screen min-w-0 ...">
+ <main class="min-h-screen flex flex-col ml-0 md:ml-[60px] ...">

关键变化:

  • 移除 h-screenmin-h-screen,内容短时至少撑满视口,内容长时自然延伸
  • 移除 flex-1(sidebar 已脱离文档流,不再参与 flex 空间分配)
  • 添加 md:ml-[60px] 为 fixed 侧边栏让出空间(仅桌面端生效)

修改 4:移除内层 div 的 overflow-y-auto

- class={`flex-1 overflow-x-hidden ${flat ? "overflow-hidden" : "overflow-y-auto scroll-smooth"}`}
+ class={`flex-1 overflow-x-hidden ${flat ? "overflow-hidden" : ""}`}

body 已接管滚动,内层 div 不再需要 overflow-y-auto

修改 5:收窄 ripple 效果的 CSS 选择器

- button, a {
-   position: relative;
-   overflow: hidden;
- }
+ .ripple-btn, .ripple-link {
+   position: relative;
+   overflow: hidden;
+ }

从全局选择器改为特定 class,需要波纹效果时显式添加 .ripple-btn.ripple-link

修改 6:防御性样式

globals.css 中添加显式宽度约束,防止意外继承:

html, body {
  width: 100%;
  max-width: 100%;
}

#main-content-container {
  max-width: none;
}

最终结构

body                          ← 正常滚动,overflow-hidden 已移除
├── aside.fixed.left-0.h-screen ← 固定左侧,始终可见
└── main.min-h-screen.md:ml-[60px]
      ├── header.sticky.top-0     ← 滚动时吸顶
      └── div.flex-1
            └── #main-content-container
                  └── 页面内容

经验总结

  1. body 的滚动应该由 body 自己处理。在 body 上设置 overflow: hidden 然后用内层 div 模拟滚动,看似精巧,实则在跨浏览器、跨设备场景下极易出问题。

  2. position: fixedposition: sticky 在 flex 容器中更可靠sticky 在 flex 子元素上行为不一致,尤其当父容器高度动态变化时。

  3. 全局 CSS 选择器要审慎button, a { overflow: hidden } 这种写法影响范围太广,应使用特定 class 控制。

  4. 本地 dev 和线上 production 的差异点需要关注:Vite dev server 注入 CSS 的方式与 Astro build 后的合并顺序不同,可能导致同样的代码在两种环境下表现不一致。