三栏布局博客的侧边栏悬浮优化与暗夜模式适配
三栏布局博客的侧边栏悬浮优化与暗夜模式适配
1. 概述
Blog-SSR 从单栏布局升级为三栏布局(左侧边栏 / 主内容区 / 右侧边栏)后,侧边栏的体验细节成为需要打磨的重点。本文记录了两项核心优化:
- 悬浮固定:侧边栏在页面滚动时保持可见,不随内容流消失。
- 暗夜模式适配:移除硬编码的 Tailwind
dark:类,统一使用 CSS 自定义属性(变量),实现主题一键切换。
2. 问题分析
2.1 滚动消失问题
初始三栏布局的 HTML 结构如下:
<div class="lg:grid lg:grid-cols-[220px_1fr_240px] lg:gap-8">
<aside><!-- 左侧边栏卡片 --></aside>
<main><!-- 页面内容 --></main>
<aside><!-- 右侧边栏卡片 --></aside>
</div>
侧边栏卡片直接放在 grid 子项中,没有 position: sticky,当主内容滚动时,两侧随整体流式布局一起滚出视口。用户阅读长文时需要反复滚动到页面顶部才能使用导航/目录,体验较差。
2.2 暗夜模式硬编码
原 SideBarCard 组件使用 Tailwind 的 dark: 变体:
class: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5 shadow-sm",
这种方式有两个问题:
- 与全局主题方案割裂:项目使用 CSS 变量 +
data-theme属性切换主题(light/dark),而非 Tailwind 的class策略。dark:变体依赖prefers-color-scheme媒体查询或.dark类,与项目的[data-theme="dark"]选择器不匹配。 - 维护负担:每个样式属性都需要同时写 light 和 dark 两个值,修改主题时需多处同步更新。
3. 悬浮固定实现
3.1 方案选择
| 方案 | 说明 | 选择 |
|------|------|------|
| position: fixed | 脱离文档流,位置相对于视口 | ❌ 会与 grid 布局冲突,宽度需手动计算 |
| position: sticky | 在父容器内固定,宽度由 grid 自动分配 | ✅ 与 grid 完美配合 |
3.2 实现细节
在侧边栏的 aside 内部增加一个粘性容器:
aside { class: "hidden lg:block w-full",
div { class: "sticky top-24 max-h-[calc(100vh-8rem)] overflow-y-auto space-y-5",
style: "scrollbar-width:thin; scrollbar-color:var(--border) transparent;",
// ... 卡片内容
}
}
关键属性说明:
sticky top-24:元素在滚动到距离视口顶部24 * 0.25rem = 6rem(96px)时粘住,与 64px 高的导航栏 + 32px 间隙对齐。max-h-[calc(100vh-8rem)]:最大高度为视口高度减去 8rem(128px),确保底部不被裁切。overflow-y-auto:当侧边栏内容超长时启用内部滚动。scrollbar-width:thin:Firefox 细滚动条,减少视觉干扰。
为什么用
sticky而不是fixed?
fixed会脱离 grid 流,宽度需写死(220px / 240px),在小屏/缩放下行为异常。sticky保留在 grid 单元格内,宽度由grid-template-columns自动分配,响应式无需额外处理。
4. 暗夜模式适配
4.1 项目的主题变量体系
项目的主题变量定义在 templates/index.html 中:
:root {
--bg: #eff1f5; --card: #e6e9ef;
--border: #ccd0da; --text: #4c4f69;
--secondary: #5c5f77; --tertiary: #9ca0b0;
--primary: #1e66f5; --primary-light: #d6e4fb;
--hover: #ccd0da;
}
[data-theme="dark"] {
--bg: #1e1e2e; --card: #181825;
--border: #313244; --text: #cdd6f4;
--secondary: #bac2de; --tertiary: #6c7086;
--primary: #89b4fa; --primary-light: #2a4a7a;
--hover: #313244;
}
所有颜色通过 var(--name) 引用,[data-theme="dark"] 下的变量值会在用户切换主题时自动覆盖。
4.2 SideBarCard 重构
修改前:
class: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5 shadow-sm",
h3 { class: "font-bold mb-3", {title} }
修改后:
class: "rounded-lg border p-5 shadow-sm",
style: "background:var(--card); border-color:var(--border);",
h3 { class: "font-semibold mb-3 text-sm tracking-wide", style: "color:var(--secondary);", {title} }
对比效果:
| 样式 | 修改前 Light | 修改前 Dark | 修改后(CSS 变量) |
|------|-------------|-------------|-------------------|
| 卡片背景 | #ffffff | #1f2937 | var(--card) → #e6e9ef / #181825 |
| 卡片边框 | #e5e7eb | #374151 | var(--border) → #ccd0da / #313244 |
| 标题颜色 | inherit(黑色) | inherit(白色) | var(--secondary) → #5c5f77 / #bac2de |
4.3 侧边栏链接与标签
所有交互元素统一使用主题变量:
/* assets/main.css */
.sidebar-link:hover {
background: var(--hover);
color: var(--text) !important;
}
计数标签(分类文章数、归档条目数)使用:
style: "background:var(--primary-light); color:var(--primary);",
5. 优化效果
5.1 滚动体验
| 场景 | 优化前 | 优化后 |
|------|--------|--------|
| 阅读长文 | 侧边栏随内容消失,需回顶部操作 | 侧边栏悬浮固定,导航/目录始终可见 |
| 多标签页跳转 | 每次需滚动回顶 | 任意位置可直接点击 |
| 窄屏(< 1024px) | 侧边栏隐藏 | 同左(未改变) |
5.2 主题切换
Light: ┌─────────────────────┐ Dark: ┌─────────────────────┐
│ background: #e6e9ef │ │ background: #181825 │
│ border: #ccd0da │ ⇄ │ border: #313244 │
│ color: #5c5f77 │ │ color: #bac2de │
└─────────────────────┘ └─────────────────────┘
所有卡片会在用户点击 🌙 切换按钮时瞬间统一变色,无需刷新页面。
5.3 可维护性提升
- 颜色值集中管理在
index.html的 CSS 变量中,修改主题只需改变量值。 - 侧边栏组件无需关心具体色值,只引用
var(--xxx)。 - 未来要新增主题(如 Sepia 护眼模式),只需在 CSS 中新增一组变量。
6. 总结
两项优化都遵循了渐进增强的原则:在小屏设备上侧边栏原本就隐藏,行为无变化;在大屏上 sticky 提供了更好的滚动体验。暗夜模式的改造则让组件与项目的全局主题系统对齐,消除了一处技术债。这些细节打磨虽然不改变功能,但对日常使用体验的提升是显著的。