<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>博客重构记录 · Cytrogen 的个人博客</title><meta name="description" content="详细记录了一次 Hexo 主题的深度重构,旨在分享如何减少对 JavaScript 的依赖,并回归现代 CSS 与 HTML 的原生能力。文章深入探讨了高级字体优化(CLS 缓解与视觉对齐)、利用 color-mix() 构建动态颜色系统、以及使用内联 SVG 替代字体图标等性能实践,为希望精简技术栈、提升网站性能的开发者提供了宝贵的实战经验。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/7f58.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
font-family: 'Open Sans';
src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
size-adjust: 107%;
ascent-override: 97%;
descent-override: 25%;
line-gap-override: 0%;
}
</style><script>(function() {
try {
// 优先级:用户选择 > 系统偏好 > 默认浅色
const saved = localStorage.getItem('theme');
const theme = saved ||
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
} catch (error) {
// 失败时使用默认主题,不阻塞渲染
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
document.addEventListener('DOMContentLoaded', function() {
const theme = document.documentElement.getAttribute('data-theme');
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper && theme) {
pageWrapper.setAttribute('data-theme', theme);
}
});
})();
</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/7f58.html">永久链接</a><div class="p-summary visually-hidden"><p>重构博客网站,也就是该网站的记录。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/Hexo/">Hexo</a><a class="p-category" href="../tags/CSS/">CSS</a></div><h1 class="post-title p-name">博客重构记录</h1><div class="post-info"><time class="post-date dt-published" datetime="2025-09-14T04:00:00.000Z">9/14/2025</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:55.213Z"></time></div><div class="post-content e-content"><html><head></head><body><p>重构博客网站,也就是该网站的记录。</p>
<span id="more"></span>
<h2 id="多用-css"><a class="markdownIt-Anchor" href="#多用-css"></a> 多用 CSS</h2>
<p>近期订阅了一些英语的周刊,主要是前端相关,里面收集了很多外网博客平台上的优秀文章。其中不乏有一篇文章吸引到了我:<a target="_blank" rel="noopener" href="https://lyra.horse/blog/2025/08/you-dont-need-js/">很多时候你根本不需要使用 JavaScript</a>。</p>
<p>吸引我的理由很简单。我的前端技术栈主要是 React.JS。相较于传统的「网页三剑客」,React.JS 这类现代前端框架需要客户端下载并执行更多的 JavaScript 代码。其核心的虚拟 DOM 技术虽然在过去带来了性能优势,但在现代浏览器性能已大幅提升的今天,其初始化和运行时成本有时反而不如原生方法来得直接高效。</p>
<p>但是,对过去的我而言,功能就是要用 JavaScript 才能做到。<em>纯 HTML 和 CSS 能做到什么?它们又不是脚本语言。</em> 不过在那个文章里,这个想法是片面的。现代的 CSS 技术进化很快、有着性能优异的各类方法,完全可以代替 JavaScript 来实现一些功能。</p>
<blockquote>
<p>举个例子,我们想要实现主题切换功能。</p>
<p>如果是网页三剑客的话,我们可能会想到用 JavaScript 监听切换按钮,该按钮被点击了我们就改变被监听的类或者元素的样式。</p>
<p>在 React.JS 上的话,那就是存一个主题状态:如果是 light 模式就怎么怎么样;如果是 dark 模式就怎么怎么样。如果用的还是 Material-UI 或者其他 UI 框架,那大概率还会有个 theme 配置文件。</p>
<p>这些方案对我们而言应该都很熟悉:<em>对啊,怎么了,这样做不是很正常吗。</em></p>
<p>其实可以更简单一些,用 CSS 的 <code>color-mix()</code> 方法就可以办到:</p>
<figure class="highlight scss"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="comment">// 核心品牌色</span></span><br><span class="line"> <span class="attr">--color-primary</span>: <span class="number">#5454f8</span>;</span><br><span class="line"> <span class="attr">--color-secondary</span>: <span class="number">#267B54</span>;</span><br><span class="line"> <span class="attr">--color-accent</span>: <span class="number">#4088b8</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用 color-mix() 生成交互状态</span></span><br><span class="line"> <span class="attr">--color-primary-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--color-primary) <span class="number">85%</span>, black);</span><br><span class="line"> <span class="attr">--color-secondary-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--color-secondary) <span class="number">85%</span>, black);</span><br><span class="line"> <span class="attr">--surface-interactive-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--surface-interactive) <span class="number">80%</span>, <span class="built_in">var</span>(--color-primary));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 深色主题</span></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> {</span><br><span class="line"> <span class="attr">--color-primary</span>: <span class="number">#818cf8</span>;</span><br><span class="line"> <span class="attr">--color-secondary</span>: <span class="number">#34d399</span>;</span><br><span class="line"> <span class="attr">--color-accent</span>: <span class="number">#60a5fa</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 深色模式下的交互状态</span></span><br><span class="line"> <span class="attr">--color-primary-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--color-primary) <span class="number">80%</span>, white);</span><br><span class="line"> <span class="attr">--color-secondary-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--color-secondary) <span class="number">80%</span>, white);</span><br><span class="line"> <span class="attr">--surface-interactive-hover</span>: <span class="built_in">color-mix</span>(in srgb, <span class="built_in">var</span>(--surface-interactive) <span class="number">70%</span>, <span class="built_in">var</span>(--color-primary));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>又或者,我们想要创建模态框。通常我们会写一个 <code><div></code>,然后写 JavaScript 来控制它的显示和隐藏、背景遮罩、锁定用户的键盘焦点、处理 <code>Esc</code> 键退出等等等等。实际上,可以直接用 <code><dialog></code> 来写、用 <code>::backdrop</code> 伪元素给背景遮罩添加样式和动画、用 <code>@starting-style</code> 来实现更流畅的入场动画。</p>
<p>其他交互效果,例如按钮的颜色变化就更不用说了。这里还有一个很有意思的考量:JavaScript 很可能带来安全问题,而 CSS 是完全安全的。</p>
</blockquote>
<p>带着这些全新的知识点,我重构了我的博客网站。其实之前我对于网站设计是没有一丝考量的,也不在乎 CSS 这些让我觉得「不如 React.JS 优雅高级」的技术,因此我的博客网站性能一般,对比原本主题的设计还添加了许多非必需的东西。我的顶部菜单栏出现过明显的元素堆积过多的情况,一些按钮很丑很奇怪、像是四不像、哪儿哪儿都不挨上,所以我决定先在博客网站上将部分功能撤下。其中就有搜索功能:首先它的样式我一直没有设计、难看得很;其次是这个功能我认为没必要留、不够精简。未来如果需要的话,我会想一个更好的方案。</p>
<blockquote>
<p>不过在 Hexo Theme Ares 里,搜索功能还会保留,并且进行了一次小重构。</p>
</blockquote>
<p>我还撤走了评论区的 Disqus 评论区,因为这东西多多少少会给我带来一些影响:<em>今天有没有人发评论?今天有没有人作反应?</em> 其实很没必要,本身看的人就不多。</p>
<h2 id="字体"><a class="markdownIt-Anchor" href="#字体"></a> 字体</h2>
<h4 id="正确导入字体"><a class="markdownIt-Anchor" href="#正确导入字体"></a> 正确导入字体</h4>
<p>我不只是将一些功能用 CSS 重构了,我还改了一下字体的导入方式,参考的是另一篇 <a target="_blank" rel="noopener" href="https://www.jonoalderson.com/performance/youre-loading-fonts-wrong/">优质的文章</a>,具体讲了现代网站是如何错误地导入字体的。</p>
<p>我的博客主题原先会导入足足四个字体:</p>
<ol>
<li>英语的 Open Sans</li>
<li>简体中文的 Noto Sans(其实就是思源黑体,不过我在考虑换成其他的;我个人阅读时喜欢用霞鹜文楷,但还没尝试换过,有可能和我的主题不搭配)</li>
<li>用于特殊标题的 Dosis</li>
<li>代码块用的 JetBrains Mono</li>
</ol>
<p>这些字体都是用 Google Fonts 的 CDN 导入的。其实这种方式并非最佳实践,很容易导致性能问题:浏览器会发起两次请求,第一次用于请求 <code>fonts.googleapis.com</code> 这个地址、获取一个 CSS 文件,第二次则是请求 CSS 文件内的真实字体文件地址。这个过程至少会有两次网络往返,第一次请求的 CSS 文件会阻塞渲染,这意味着在它下载完成之前,页面可能是一片空白或者没有应用任何样式。为了解决这些问题,我是这么做的:</p>
<figure class="highlight scss"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 加载真正的 Open Sans 字体</span></span><br><span class="line"><span class="keyword">@font-face</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'Open Sans'</span>;</span><br><span class="line"> <span class="attribute">src</span>: <span class="built_in">url</span>(<span class="string">'...'</span>) <span class="built_in">format</span>(<span class="string">'woff2'</span>);</span><br><span class="line"> <span class="attribute">font-weight</span>: <span class="number">400</span>;</span><br><span class="line"> <span class="attribute">font-style</span>: normal;</span><br><span class="line"> <span class="attribute">font-display</span>: swap;</span><br><span class="line"> unicode-range: U+<span class="number">0000</span>-<span class="number">00</span>FF, U+<span class="number">0131</span>, U+<span class="number">0152</span>-<span class="number">0153</span>, U+<span class="number">02</span>BB-<span class="number">02</span>BC, U+<span class="number">02</span>C6, U+<span class="number">02</span>DA, U+<span class="number">02</span>DC, U+<span class="number">2000</span>-<span class="number">206</span>F, U+<span class="number">2074</span>, U+<span class="number">20</span>AC, U+<span class="number">2122</span>, U+<span class="number">2191</span>, U+<span class="number">2193</span>, U+<span class="number">2212</span>, U+<span class="number">2215</span>, U+FEFF, U+FFFD;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 创建“替身”字体</span></span><br><span class="line"><span class="keyword">@font-face</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'Open Sans Fallback'</span>;</span><br><span class="line"> <span class="attribute">src</span>: <span class="built_in">local</span>(<span class="string">'Arial'</span>); <span class="comment">// 使用人人都有的 Arial 字体</span></span><br><span class="line"> <span class="attribute">font-weight</span>: <span class="number">400</span>;</span><br><span class="line"> <span class="attribute">font-style</span>: normal;</span><br><span class="line"> <span class="attribute">font-display</span>: swap;</span><br><span class="line"> <span class="comment">// 强行让 Arial 的尺寸变得和 Open Sans 一样</span></span><br><span class="line"> size-adjust: <span class="number">107%</span>;</span><br><span class="line"> ascent-override: <span class="number">97%</span>;</span><br><span class="line"> descent-override: <span class="number">25%</span>;</span><br><span class="line"> <span class="selector-tag">line</span>-<span class="attribute">gap</span>-override: <span class="number">0%</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight scss"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 3. 在字体栈里,让“替身”字体紧跟在真正的 Open Sans 字体之后</span></span><br><span class="line"><span class="variable">$base-font-family</span>: <span class="string">'Open Sans'</span>, <span class="string">'Open Sans Fallback'</span>, <span class="string">'Noto Sans SC'</span>, -apple-system, BlinkMacSystemFont, <span class="string">'Segoe UI'</span>, Roboto, <span class="string">'Helvetica Neue'</span>, Arial, <span class="string">'PingFang SC'</span>, <span class="string">'Hiragino Sans GB'</span>, <span class="string">'Microsoft YaHei'</span>, sans-serif;</span><br></pre></td></tr></tbody></table></figure>
<p>这个思路很简单:先让浏览器用这个调整好尺寸的 Arial 字体把字体显示出来,因为是本地字体,所以速度会很快、文字的位置会迅速固定。当真正的 Open Sans 字体下载好之后,浏览器就会立刻用它替换掉临时的 Arial「替身」。由于位置和尺寸早就被 Arial 字体固定好,所以整个替换过程在视觉上是无缝的,页面完全不会跳动。</p>
<p>再就是字体格式,相较于 TFF 或 OTF 等传统格式,用 WOFF2 更好。WOFF2 的压缩率极高,兼容性也特别好,尽量开发网页时都采取这个字体格式。不过有些字体不让转换成这个格式,需要特别注意一下。</p>
<p>你也可以看出来,我用了 <code>unicode-range</code> 控制了字体的字符范围、只让页面加载它实际需要的字符。比如说 Open Sans 只加载基本拉丁字符、标点符号和货币符号,Noto Sans 则只包含中文汉字字符范围(<code>unicode-range: U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF;</code>)。</p>
<h4 id="不同字体的视觉对齐"><a class="markdownIt-Anchor" href="#不同字体的视觉对齐"></a> 不同字体的视觉对齐</h4>
<p>作为一个中文技术博客,文章中难免会出现英文单词以及代码片段。为了达到最佳的阅读体验,我会为中文字符、西文(拉丁)字符以及代码用的字符分别指定一个字体。然而这里会面临一个问题:不同的字体,即时 <code>font-size</code> 是一致的,但它们在视觉上的大小、重心和基线位置往往是不一样的。当这些中英字符同时出现在一行时,就会显得大小不一、高低错落,破坏了排版的和谐感。</p>
<blockquote>
<p>此处令我想到高中时、一位我很喜欢的老师。当时她还不是正式教师,考核时给我们上的课讲的就是排版和字体。当时并没有认真听讲。</p>
</blockquote>
<p>CSS 提供了一些属性,恰好可以让我们解决这个问题:</p>
<figure class="highlight scss"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@font-face</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'JetBrains Mono'</span>;</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> size-adjust: <span class="number">94%</span>;</span><br><span class="line"> ascent-override: <span class="number">92%</span>;</span><br><span class="line"> descent-override: <span class="number">22%</span>;</span><br><span class="line"> <span class="selector-tag">line</span>-<span class="attribute">gap</span>-override: <span class="number">0%</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>我通过 <code>size-adjust: 94%</code> 将 JetBrains Mono 的整体视觉大小缩小了一点,然后用 <code>ascent-override</code> 等属性微调了它的垂直对齐基线,最终让它和我的正文字体放在一起时,看起来没那么突兀了。</p>
<h4 id="流式排版"><a class="markdownIt-Anchor" href="#流式排版"></a> 流式排版</h4>
<p>每个人阅读博客的设备都各不相同,有用电脑的,有用平板的,有用手机的,有用小天才手表的(<em>有吗?</em>)。不同设备的屏幕尺寸不同,我们通常需要在多个端点处使用媒体查询来手动调整 <code>font-size</code>。例如:</p>
<figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">768px</span>) { <span class="selector-tag">body</span>: font-size: <span class="number">17px</span>; } }</span><br></pre></td></tr></tbody></table></figure>
<p>这很繁琐,要知道现在世界上有多少种屏幕尺寸,一个个适配过去会累死掉。更优雅的解决方案是采用流式排版,即让字体大小像液体一样,随着屏幕宽度的变化而平滑、无缝地缩放,确保在任意设备宽度下都有最佳的视觉表现。实现这种效果,要用到 <code>clamp()</code> 方法。</p>
<figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="attr">--font-size-body</span>: <span class="built_in">clamp</span>(<span class="number">1rem</span>, <span class="number">0.9rem</span> + <span class="number">0.5vw</span>, <span class="number">1.125rem</span>); <span class="comment">/* 从16px平滑过渡到18px */</span></span><br><span class="line"> <span class="attr">--font-size-h1</span>: <span class="built_in">clamp</span>(<span class="number">2rem</span>, <span class="number">1.5rem</span> + <span class="number">2.5vw</span>, <span class="number">3rem</span>); <span class="comment">/* 从32px平滑过渡到48px */</span></span><br><span class="line"> // ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p><code>clamp()</code> 的三个参数分别是:</p>
<ul>
<li>最小值</li>
<li>首选值</li>
<li>最大值</li>
</ul>
<h2 id="告别-jquery-和-font-awesome"><a class="markdownIt-Anchor" href="#告别-jquery-和-font-awesome"></a> 告别 jQuery 和 Font Awesome</h2>
<p>优化完字体系统后,可以想想图标字体该怎么办。我的原先方案是用 Font Awesome,实际上也不是一个很好的选择。Font Awesome 是用传统的网络请求加载的,也会导致不必要的性能和阻塞渲染问题。最好的方案是使用 SVG。很多时候我们的网站只会用到一些图标字体,为此下载一整个完整字体库很没必要,SVG 的可访问性也更好。</p>
<p>我同时又检查了一遍博客的代码,发现主题的所有功能都被原生 JavaScript 实现。因此我移除了 jQuery 依赖。其实我都不是很明白这个主题哪里用到了 jQuery,可能是主题作者先前留下的。</p>
<h2 id="提升交互体验"><a class="markdownIt-Anchor" href="#提升交互体验"></a> 提升交互体验</h2>
<div class="reply-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><time class="dt-published visually-hidden" datetime="2026-02-19T08:33:14.188Z">2026-02-19T08:33:14.188Z</time><a class="u-url visually-hidden" href="https://cytrogen.icu">Post Link</a><div class="reply-content e-content"><p>在阅读了 <a target="_blank" rel="noopener" href="https://taxodium.ink/my-blog-design.html">这篇文章</a> 之后,我为我的网站添加了一些可以交互的内容。主要添加的是一个「返回顶部」按钮,移动端上它会显示在顶部居中的位置,桌面端则固定显示在右下角。</p>
</div><div class="reply-meta p-in-reply-to h-cite"><span class="reply-label">回复:</span><a class="reply-target u-url" target="_blank" rel="noopener" href="https://taxodium.ink/my-blog-design.html">https://taxodium.ink/my-blog-design.HTML</a></div></div>
<p>先前在 <a href="#%E5%A4%9A%E7%94%A8-css">这一节</a> 里的例子里提到的动画、基于 <code><details></code> 的菜单我也实现了。</p>
<h2 id="无障碍增强"><a class="markdownIt-Anchor" href="#无障碍增强"></a> 无障碍增强</h2>
<p>我几乎是不会考虑「无障碍」的,过去的时候。在阅读了一些博客网站的设计想法时我意识到,实际上用着「不寻常设备」的读者可能比想象中还要多,总是要确保所有的用户都能良好地使用博客功能。当然,不只是博客,未来开发的网站也要想到这一点才行。这次重构中,我添加了一个跳过导航的按钮,它平日会被藏在屏幕视图的上方,需要使用 Tab 才可以将其唤出。Tab 也可以用来快速导航到下一个标题、链接、代码块等。</p>
<p>我也为正文链接添加了下划线,有用到 <code>text-decoration-skip-ink: auto</code> 这个属性,可以自动跳过字形,例如拉丁字符 <code>g</code>、<code>j</code> 等。</p>
</body></html></div></article></div></main><footer><div class="paginator"><a class="prev" href="b52f.html">上一篇</a><a class="next" href="369e.html">下一篇</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/7f58.html" data-full-url="https://cytrogen.icu/posts/7f58.html" data-mode="static">
<h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
<div class="webmention-list"></div>
<span>暂无 Webmentions</span>
</div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
const codeBlocks = document.querySelectorAll('figure.highlight');
codeBlocks.forEach(block => {
let caption = block.querySelector('figcaption');
if (!caption) {
caption = document.createElement('figcaption');
block.insertBefore(caption, block.firstChild);
}
const info = document.createElement('div');
info.className = 'info';
const filename = caption.querySelector('span');
if (filename) {
filename.className = 'filename';
info.appendChild(filename);
}
const lang = block.className.split(' ')[1];
if (lang) {
const langSpan = document.createElement('span');
langSpan.className = 'lang-name';
langSpan.textContent = lang;
info.appendChild(langSpan);
}
const sourceLink = caption.querySelector('a');
if (sourceLink) {
sourceLink.className = 'source-link';
info.appendChild(sourceLink);
}
const actions = document.createElement('div');
actions.className = 'actions';
const codeHeight = block.scrollHeight;
const threshold = 300;
if (codeHeight > threshold) {
block.classList.add('folded');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '展开';
toggleBtn.addEventListener('click', () => {
block.classList.toggle('folded');
toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
});
actions.appendChild(toggleBtn);
}
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制';
copyBtn.addEventListener('click', async () => {
const codeLines = block.querySelectorAll('.code .line');
const code = Array.from(codeLines)
.map(line => line.textContent)
.join('\n')
.replace(/\n\n/g, '\n');
try {
await navigator.clipboard.writeText(code);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 3000);
} catch (err) {
console.error('复制失败:', err);
copyBtn.textContent = '复制失败';
setTimeout(() => {
copyBtn.textContent = '复制';
}, 3000);
}
});
actions.appendChild(copyBtn);
caption.innerHTML = '';
caption.appendChild(info);
caption.appendChild(actions);
const markedLines = block.getAttribute('data-marked-lines');
if (markedLines) {
const lines = markedLines.split(',');
lines.forEach(range => {
if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
for (let i = start; i <= end; i++) {
const line = block.querySelector(`.line-${i}`);
if (line) line.classList.add('marked');
}
} else {
const line = block.querySelector(`.line-${range}`);
if (line) line.classList.add('marked');
}
});
}
});
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.querySelector('.theme-toggle');
if (!themeToggle) return;
const getCurrentTheme = () => {
return document.documentElement.getAttribute('data-theme') || 'light';
};
const updateUI = (theme) => {
const isDark = theme === 'dark';
themeToggle.setAttribute('aria-pressed', isDark.toString());
};
const setTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper) {
pageWrapper.setAttribute('data-theme', theme);
}
// Find and remove the temporary anti-flicker style tag if it exists.
// This ensures the main stylesheet takes full control after the initial load.
const antiFlickerStyle = document.getElementById('anti-flicker-style');
if (antiFlickerStyle) {
antiFlickerStyle.remove();
}
localStorage.setItem('theme', theme);
updateUI(theme);
};
const toggleTheme = () => {
const current = getCurrentTheme();
const newTheme = current === 'light' ? 'dark' : 'light';
setTheme(newTheme);
};
updateUI(getCurrentTheme());
themeToggle.addEventListener('click', toggleTheme);
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
if (!localStorage.getItem('theme')) {
const theme = e.matches ? 'dark' : 'light';
setTheme(theme);
}
});
}
});
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const backToTopBtn = document.querySelector('.back-to-top');
if (!backToTopBtn) return;
const toggleButtonVisibility = () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const shouldShow = scrollTop > 200;
if (shouldShow) {
backToTopBtn.classList.add('is-visible');
} else {
backToTopBtn.classList.remove('is-visible');
}
};
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
toggleButtonVisibility();
ticking = false;
});
ticking = true;
}
};
const scrollToTop = (event) => {
event.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
window.addEventListener('scroll', handleScroll);
backToTopBtn.addEventListener('click', scrollToTop);
toggleButtonVisibility();
});
})();</script></body></html>