暂不支持移动端访问

AstroBlog 前端样式、主题与页面过渡

1943 字 10 分钟
AI 摘要

从 CSS 分层、语义令牌和主题初始化,到 Swup 页面切换后的脚本生命周期。

AstroBlog 的视觉样式分成两套需要同时维护的系统:CSS 负责静态外观,Swup 负责无完整刷新页面时的生命周期。真正容易出问题的是页面切换以后旧 DOM、旧 class 和旧事件监听仍然存在,因此这里重点说明样式入口、主题状态和页面过渡如何协作。

CSS 入口和层顺序#

全局入口是 src/styles/main.css。它不承载具体页面样式,只负责 Tailwind、预置样式、设计令牌和分层文件的导入。Layout.astro 在 HTML 中声明:

CSS
/* 共享主题规则位于组件样式之后、最终页面覆盖之前。 */
@layer theme, reset, tokens, base, components, utilities, overrides;

颜色、排版、间距、圆角和 z-index 等跨组件值放在 src/styles/tokens/;跨页面的基础元素放在 base/;公共组件放在 components/;具体页面放在 pages/;后台则有独立的 admin/ 分层。新增样式时先判断它属于令牌、公共组件还是页面特例,再选择对应文件。

颜色使用语义变量,而不是在组件里重复十六进制值:

CSS
.panel {
background: var(--card-bg);
color: var(--deep-text);
border: 1px solid var(--border-default);
}

浅色和深色主题通过 :root.dark 覆盖同一组变量。组件只消费 --card-bg--deep-text--border-default 等语义名,因此修改主题时不需要逐个搜索组件。新增变量需要同时加入浅色、深色和验证脚本使用的契约。

主题初始化流程#

主题有 lightdarksystem 三种状态。Layout.astro 的内联脚本在渲染前读取 localStorage,并把结果写入 document.documentElement;页面可见后,setting-utils.tssetTheme() 负责更新 class、data 属性和存储值,initThemeListener() 监听系统主题变化。

首屏脚本不能依赖尚未加载的组件。主题基础状态必须在 <head> 阶段完成,否则页面会先以浏览器默认颜色绘制,再跳到博客主题。运行时脚本可以继续操作壁纸模式、字体和其他设置,但不能重复定义主题状态。

Swup 的容器和生命周期#

astro.config.mjs 中的 Swup 配置同时替换 #swup-container、Banner 覆盖层和左右侧栏动态容器。音乐页、后台和独立页面被显式忽略,因为它们使用不同的布局骨架,不能把主站容器强行塞进去。

text
点击链接
content:replace 替换容器并同步布局 class
page:view 初始化主题、评论、Mermaid、目录等页面脚本

点击阶段只处理导航反馈,不能过早查询新页面 DOM;content:replace 之后才依据新路径调整 Banner 高度和侧栏状态;page:view 适合启动依赖完整内容的功能。将初始化代码放在错误阶段,会出现内容已经替换但脚本仍引用旧节点的情况。

事件清理和重复初始化#

需要在页面切换后重新运行的脚本,通常使用 AbortController 保存监听器生命周期。页面被替换时调用 cleanup,终止 resize、scroll、布局切换和自定义事件监听,再由新页面建立新的 controller。没有清理的监听器会在连续切换页面后叠加,表现为同一个请求发送多次或一个动画执行多遍。

文章列表的瀑布流就是一个典型例子。PostPage.astro 在脚本重新执行前调用上一次清理函数,监听 astro:before-swap,并在图片加载、窗口 resize、布局切换和封面可见性改变时重新计算。新增页面脚本时,应当沿用这种可取消的生命周期,而不是把匿名监听器永久挂到 window

动画和可访问性#

Swup 只负责容器切换,组件自己的动画仍由 CSS transition、requestAnimationFrame 或 GSAP 处理。页面过渡动画不能依赖元素高度变化,否则侧栏和 Banner 的尺寸变化会触发跳动。设置 prefers-reduced-motion 时应关闭不必要的位移、自动播放和复杂过渡,保留内容可见性和焦点移动。

导航和弹窗在页面切换后需要重新建立焦点关系。Swup 配置打开 accessibility,但具体组件仍要提供按钮语义、关闭方式和可见焦点。新增脚本时,不要只测试鼠标点击,还要测试键盘从旧页面切换到新页面后焦点是否落在可操作元素上。

修改样式时的检查顺序#

修改颜色或字体,先查 src/styles/tokens/;修改卡片、按钮或浮层,查 components/ 和 z-index 令牌;修改单页布局,查 pages/;修改后台界面,查 admin/;修改页面切换逻辑,查 astro.config.mjsLayout.astro 和相关组件的 cleanup。

Terminal windowpowershell
# 检查 CSS 层级、令牌、z-index 和紧凑文字契约。
pnpm verify:styles
# 运行 Astro 模板和内容集合类型检查。
pnpm check
# 执行主站浏览器回归,包括 Swup、主题和首页壁纸场景。
pnpm verify:browser

样式检查通过只能说明令牌和层级契约没有被破坏,不能替代浏览器回归。涉及 Swup、主题、侧栏或壁纸时,应在首页、文章列表和文章详情之间检查刷新、后退、前进、移动端宽度和键盘焦点。

层级令牌和浮层关系#

浮动控制栏、搜索框、评论图片灯箱、后台弹窗和壁纸覆盖层都使用 src/styles/tokens/z-index.css 的语义变量。新增浮层时先确定它属于页面内容、导航、模态框还是全屏遮罩,再使用对应的 --z-* 令牌。直接写一个更大的 z-index 只能暂时解决遮挡,页面切换或嵌套弹窗时仍可能出现层级倒置。

Swup 脚本的最小模式#

页面专用脚本应当有“初始化 + 清理”两部分:初始化查找当前容器并绑定监听器,清理取消监听器、计时器和动画。可以使用以下结构:

JavaScript
// 一个 controller 统一管理当前 Swup 页面创建的监听器。
const controller = new AbortController();
window.addEventListener('resize', recalculate, { signal: controller.signal });
// Swup 替换容器前统一中止,避免旧 DOM 监听器残留。
document.addEventListener('astro:before-swap', () => controller.abort(), { once: true });

不要把 DOM 节点保存到全局变量后跨页面复用。Swup 替换容器后,旧节点已经不属于当前文档;缓存旧节点会造成事件触发但界面没有变化,甚至阻止垃圾回收。

CSS 和 Tailwind 的取舍#

Tailwind 工具类适合页面级组合,分层 CSS 适合跨组件契约和复杂状态。一个组件同时需要主题变量、动画状态和响应式断点时,通常把基础布局放在组件样式,把颜色和层级放在 tokens,不要在模板中堆叠所有条件类。修改后要同时检查生产压缩,因为 Tailwind 只会保留扫描到的类名,动态拼接的类必须有静态候选或改用 CSS 变量。

页面切换后的表单和媒体#

评论、搜索和音乐等组件可能持有输入值、AbortController 或 MediaElement。Swup 替换容器前要取消未完成的请求,替换后重新查找表单和媒体节点;如果播放器属于全局布局,则不能随着正文容器销毁。新增页面级表单时,先确认它在主容器还是独立布局中,再决定生命周期由组件自己管理还是由 Layout 统一管理。

样式回归清单#

修改样式后检查浅色/深色、桌面/移动、主站/后台、首次加载/Swup 切换、普通页面/文章页和有无壁纸六种组合。verify:styles 只能检查约定的变量和层级,字体加载、文本溢出、焦点可见性和动画时序仍需要浏览器验证。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 目录 ]
[ 全部文章 ]