博客上线后最频繁的操作不是写文章,而是改样式。记录一下几轮迭代做了什么。
第一版:白板
最初只有一个 global.css,几条基础样式,内容能显示就行。首页就是一个 <ul> 列表,文章页一行标题接正文。谈不上好看,也说不上丑,只能说——能用。
第二轮:卡片化和导航
最早想改的是导航栏。加了 position: sticky 和 backdrop-filter: blur(16px),导航固定在顶部且带半透明毛玻璃效果,比跟着页面滚走舒服多了。
接着把首页的文章列表改成卡片布局。每篇文章一张独立卡片,白色背景、圆角、微阴影。hover 时卡片上浮 3px、左侧出现渐变装饰条、箭头滑入——这些细节变更成本很低,但让页面一下子有了”做完”的感觉。
CSS 变量是个好习惯,这轮一次性把颜色、阴影、圆角、字体都抽成了变量:
:root {
--color-bg: #f1f5f9;
--color-surface: #ffffff;
--color-primary: #4f46e5;
--gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06);
--radius-md: 12px;
/* ... */
}
后期加暗色模式时直接受益于此——改一行属性选择器就够了,不用满文件翻颜色值。
第三轮:文章页目录
文章页原来就是标题 + 时间 + 正文,内容长了滚起来费劲。用 IntersectionObserver 做了一个右侧粘性目录:
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const link = tocList.querySelector(`a[href="#${entry.target.id}"]`);
if (entry.isIntersecting) {
allLinks.forEach((l) => l.classList.remove("active"));
link?.classList.add("active");
}
});
},
{ rootMargin: "-80px 0px -70% 0px" }
);
随滚动高亮当前段落的目录项,不用引入任何第三方库。
第四轮:暗色模式、标签、SEO
这轮改的比较多,算是一次集中完善。
暗色模式:在 :root 基础上加 [data-theme="dark"] 覆盖变量。关键是用一段内联 <script is:inline> 在 <head> 里预读 localStorage 设置主题属性,避免页面闪烁——页面渲染前就知道要亮还是暗:
<script is:inline>
(() => {
const t = localStorage.getItem("theme") || "light";
document.documentElement.setAttribute("data-theme", t);
})();
</script>
标签系统:给文章的 content schema 加了 tags: z.array(z.string()).default([])。动态路由 /tags/[tag] 用 getStaticPaths() 生成所有标签的静态页。每篇文章顶部加标签药丸,首页卡片也显示标签,点击跳转。
SEO 收尾:写了 SEO.astro 组件统一处理 <meta> 标签——Open Graph 和 Twitter Card 共用一套 props。加上 @astrojs/sitemap 自动生成站点地图,Google 能更好抓取。
踩坑
- Astro v6 的内容集合 API 和之前不兼容:
content/config.ts移动到src/content.config.ts,集合定义必须指定loader: glob(),需要稍微适应。 - 暗色模式下 blockquote 的背景透明度要单独调整,
rgba叠加在深色背景上视觉会偏亮。 - Shiki 语法高亮是 Astro 内置的,不需要额外安装包,但 CSS 里
pre的背景必须改成透明或移除,否则会盖住 Shiki 自带的主题背景。
目前的样子
卡片布局 + 毛玻璃导航 + 粘性目录 + 暗色模式切换 + 标签筛选 + 代码高亮 + SEO 标签,一个博客该有的基本都齐了。全局 CSS 一份就够了,没引入任何框架。
博客还在改,但这几轮迭代覆盖了大部分基础体验。后面如果要再加,大概是评论系统和阅读进度条。