~cytrogen/blog-public

blog-public/posts/7f58.html -rw-r--r-- 39.3 KiB
88eebf3dCytrogen Deploy 2026-02-19 08:34:27 3 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
<!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>&lt;div&gt;</code>,然后写 JavaScript 来控制它的显示和隐藏、背景遮罩、锁定用户的键盘焦点、处理 <code>Esc</code> 键退出等等等等。实际上,可以直接用 <code>&lt;dialog&gt;</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>&lt;details&gt;</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>