Skip to content

注入灵魂:文本、字体与排版

你有没有遇到过这样的情况:打开一个网页,内容明明很有价值,但读了两段就想关掉?很可能问题出在排版上——字太小、行太挤、层次不清。

好的排版是隐形的。 读者不会注意到它的存在,只会专注于内容本身。而糟糕的排版会不断提醒读者"这里不舒服",打断阅读节奏。

本章我们将通过构建一个技术博客的文章页面,系统学习 UnoCSS 的排版能力。你会发现,专业级的文字排版并不神秘,只需要理解几个核心原则。


1. 从一篇文章开始

假设我们要为一个技术博客设计文章页面。先来看看最基础的版本:

html
<article>
  <h1>深入理解 JavaScript 闭包</h1>
  <p>发布于 2024年1月15日</p>
  <p>闭包是 JavaScript 中最强大也最容易被误解的概念之一。本文将从根本原理出发,帮助你真正理解闭包。</p>
  <h2>什么是闭包?</h2>
  <p>简单来说,闭包是指一个函数能够记住并访问它的词法作用域,即使这个函数在其词法作用域之外执行。</p>
</article>

没有任何样式的文章看起来像什么?一堆挤在一起的黑字,标题和正文没有区分,完全没有阅读的欲望。

问题在哪? 缺少视觉层次。读者的眼睛需要"锚点"来快速定位信息,需要"呼吸空间"来舒适阅读。


2. 建立字体系统

第一步是选择合适的字体。不同的字体传达不同的气质。

2.1 三种基础字体

UnoCSS 提供三种字体家族,分别对应不同的使用场景。

html
<p class="font-sans">无衬线字体 —— 现代、清晰、适合屏幕阅读</p>
<p class="font-serif">衬线字体 —— 典雅、正式、适合长文阅读</p>
<code class="font-mono">等宽字体 —— 技术感、对齐性强、适合代码展示</code>

为什么要这样分? 无衬线字体在屏幕上渲染清晰,是大多数界面的首选。衬线字体在印刷品中阅读舒适,但在屏幕上小字号时可能模糊。等宽字体让每个字符宽度相同,代码对齐才整齐。

对于我们的技术博客,一个合理的选择是:

html
<article class="font-sans">
  <h1>深入理解 JavaScript 闭包</h1>
  <p>正文使用无衬线字体...</p>
  <pre><code class="font-mono">function example() {}</code></pre>
</article>

2.2 自定义品牌字体

如果默认字体不能满足品牌需求,可以通过主题配置扩展:

ts
// uno.config.ts
export default defineConfig({
  theme: {
    fontFamily: {
      heading: ['Lexend', 'sans-serif'],
      body: ['Inter', 'system-ui', 'sans-serif'],
      code: ['JetBrains Mono', 'Fira Code', 'monospace'],
    },
  },
})

现在可以这样使用:

html
<h1 class="font-heading">标题使用 Lexend 字体</h1>
<p class="font-body">正文使用 Inter 字体</p>
<code class="font-code">代码使用 JetBrains Mono</code>

为什么要分开配置? 标题和正文的阅读方式不同。标题是"扫视",需要醒目突出;正文是"逐字阅读",需要舒适耐看。不同场景选择最合适的字体。


3. 字号:建立视觉层次

字体大小是建立层次的最直接方式。一眼看去,读者就能分辨出什么是标题、什么是正文、什么是补充信息。

3.1 层次设计原则

让我们为博客文章设计一套字号层次:

html
<article class="max-w-prose mx-auto">
  <!-- 文章大标题:最醒目 -->
  <h1 class="text-4xl">深入理解 JavaScript 闭包</h1>
  
  <!-- 元信息:低调辅助 -->
  <p class="text-sm text-gray-500">发布于 2024年1月15日 · 阅读约 8 分钟</p>
  
  <!-- 导语:比正文略大,吸引阅读 -->
  <p class="text-lg">
    闭包是 JavaScript 中最强大也最容易被误解的概念之一...
  </p>
  
  <!-- 章节标题:比正文大,比主标题小 -->
  <h2 class="text-2xl">什么是闭包?</h2>
  
  <!-- 正文:基准大小 -->
  <p class="text-base">
    简单来说,闘包是指一个函数能够记住并访问它的词法作用域...
  </p>
  
  <!-- 小节标题 -->
  <h3 class="text-xl">词法作用域回顾</h3>
</article>

层次递进的逻辑是什么?text-4xl(标题)→ text-2xl(章节)→ text-xl(小节)→ text-base(正文)→ text-sm(辅助),每一级都比上一级小,但差距适中。如果主标题是 text-4xl,章节标题直接跳到 text-base,对比就太弱了。

3.2 响应式字号

在手机上,text-4xl 的标题可能太大了。使用响应式变体适配不同屏幕:

html
<h1 class="text-2xl md:text-3xl lg:text-4xl">
  深入理解 JavaScript 闭包
</h1>

为什么是这个顺序? UnoCSS 采用移动优先策略。text-2xl 是基础(手机),md:text-3xl 覆盖平板及以上,lg:text-4xl 覆盖桌面。从小屏幕逐步放大。

3.3 UnoCSS 字号体系

完整的字号从 text-xs(12px)到 text-9xl(128px),设计时常用的是中间段:

类名大小典型用途
text-xs12px标签、脚注
text-sm14px辅助信息、时间戳
text-base16px正文(基准)
text-lg18px导语、强调段落
text-xl20px小节标题
text-2xl24px章节标题
text-3xl30px页面标题
text-4xl36px大标题

如何选择? 不要想着把所有级别都用上,一篇文章通常只需要 4-5 个级别就足够。太多层次反而让人迷惑。


4. 字重:强调与区分

字重控制文字的粗细,是区分标题和正文、强调重点的关键手段。

4.1 标题与正文的区分

html
<article>
  <h1 class="text-4xl font-bold">深入理解 JavaScript 闘包</h1>
  <h2 class="text-2xl font-semibold">什么是闭包?</h2>
  <p class="text-base font-normal">
    简单来说,闭包是指一个函数能够记住并访问它的词法作用域...
  </p>
</article>

为什么不都用 font-bold 如果所有内容都很"重",就没有东西能突出了。主标题用 font-bold(700),章节标题用 font-semibold(600),正文用 font-normal(400),形成递减的"视觉重量"。

4.2 行内强调

正文中需要强调某个词或短语时:

html
<p class="font-normal">
  闭包的核心在于<span class="font-semibold">词法作用域</span>和
  <span class="font-semibold">函数作为一等公民</span>这两个概念。
</p>

为什么用 font-semibold 而不是 font-bold?在正文中,过于强烈的对比会打断阅读节奏。font-semibold 提供了足够的强调,又不会显得突兀。

4.3 细体字的使用场景

font-thinfont-light 这些细字重适合什么时候用?

html
<!-- 超大展示文字可以用细体 -->
<h1 class="text-7xl font-thin tracking-tight">
  HELLO
</h1>

<!-- 小字号不要用细体,会不清晰 -->
<p class="text-sm font-thin">× 这样很难阅读</p>

规律是什么? 字号越大,可以用越细的字重;字号越小,需要越粗的字重来保证可读性。


5. 行高:呼吸的空间

行高决定了行与行之间的间距。这是影响阅读舒适度最重要的因素之一。

5.1 正文的行高

html
<!-- 行高太紧,阅读吃力 -->
<p class="leading-none">
  闭包是 JavaScript 中最强大也最容易被误解的概念之一。
  本文将从根本原理出发,帮助你真正理解闭包。
</p>

<!-- 行高太松,段落像散了架 -->
<p class="leading-loose">
  闭包是 JavaScript 中最强大也最容易被误解的概念之一。
  本文将从根本原理出发,帮助你真正理解闭包。
</p>

<!-- 刚好合适 -->
<p class="leading-relaxed">
  闭包是 JavaScript 中最强大也最容易被误解的概念之一。
  本文将从根本原理出发,帮助你真正理解闭包。
</p>

怎么判断"合适"? 一般规则是:正文使用 1.5 到 1.75 倍行高(leading-normalleading-relaxed)。行宽越宽,行高应该越大,帮助眼睛找到下一行的起点。

5.2 标题的行高

标题通常是短文本,不需要那么大的行高:

html
<!-- 多行标题用紧凑行高 -->
<h1 class="text-4xl font-bold leading-tight">
  深入理解 JavaScript 闭包:从原理到实践
</h1>

<!-- 单行标题可以更紧 -->
<h1 class="text-4xl font-bold leading-none">
  闭包详解
</h1>

为什么标题可以用更小的行高? 大字号本身就有足够的视觉份量,不需要额外的行间距来分隔。而且紧凑的行高让多行标题看起来更像一个整体。

5.3 行高的参考值

类名倍数适用场景
leading-none1单行大标题
leading-tight1.25多行标题
leading-snug1.375短段落
leading-normal1.5标准正文
leading-relaxed1.625长段落、宽行宽
leading-loose2特殊效果

6. 字间距:精细调节

字间距(letter-spacing)是排版中的"微调"工具,用得好能提升品质感。

6.1 大标题的字间距

大号字体的默认字间距往往显得松散,收紧一点更紧凑有力:

html
<h1 class="text-5xl font-bold tracking-tight">
  JAVASCRIPT
</h1>

<!-- 对比默认效果 -->
<h1 class="text-5xl font-bold tracking-normal">
  JAVASCRIPT
</h1>

6.2 全大写文本的字间距

全大写字母之间的间距需要加大,否则太拥挤:

html
<!-- 标签、徽章常用全大写 -->
<span class="text-xs uppercase tracking-widest font-semibold text-gray-500">
  新功能
</span>

为什么全大写需要加宽字间距? 大写字母比小写字母更"方正",彼此之间需要更多空间才能清晰分辨。

6.3 字间距使用建议

html
<!-- 大标题:收紧 -->
<h1 class="text-4xl tracking-tight">标题</h1>

<!-- 正文:默认即可 -->
<p class="tracking-normal">正文内容</p>

<!-- 全大写小文字:加宽 -->
<span class="text-xs uppercase tracking-wider">LABEL</span>

7. 文本对齐与排列

7.1 长文本始终左对齐

html
<!-- 正确:左对齐便于阅读 -->
<p class="text-left">
  闭包是 JavaScript 中最强大也最容易被误解的概念之一。
  本文将从根本原理出发,帮助你真正理解闭包。
</p>

<!-- 错误:两端对齐会产生不规则的词间距 -->
<p class="text-justify">
  闭包是 JavaScript 中最强大也最容易被误解的概念之一。
  本文将从根本原理出发,帮助你真正理解闭包。
</p>

为什么不用两端对齐? 中英文混排时,text-justify 会产生奇怪的空白。英文长单词也会被拉伸。左对齐是最安全的选择。

7.2 居中对齐的使用场景

html
<!-- 页面标题可以居中 -->
<header class="text-center">
  <h1 class="text-4xl font-bold">深入理解闭包</h1>
  <p class="text-gray-500 mt-2">发布于 2024年1月15日</p>
</header>

<!-- 短引用可以居中 -->
<blockquote class="text-center text-xl italic">
  "程序是写给人读的,顺便让机器执行。"
</blockquote>

居中对齐适合短文本和需要强调的独立元素,不适合长段落。

7.3 右对齐的使用场景

html
<!-- 数字、日期、金额通常右对齐 -->
<div class="text-right tabular-nums">
  <div>$1,234.56</div>
  <div>$78.90</div>
  <div>$456.00</div>
</div>

注意 tabular-nums:它让数字使用等宽字形,这样各位数能对齐。


8. 文本装饰:链接与强调

8.1 链接样式

技术博客中链接很多,需要明确的视觉提示:

html
<!-- 基础链接 -->
<a href="#" class="text-blue-600 underline hover:text-blue-800">
  阅读更多关于闭包的内容
</a>

<!-- 低调链接 -->
<a href="#" class="text-gray-600 hover:text-blue-600 hover:underline">
  相关文章
</a>

<!-- 自定义下划线 -->
<a href="#" class="text-blue-600 underline decoration-blue-300 decoration-2 underline-offset-4 hover:decoration-blue-600">
  精心设计的下划线
</a>

为什么要 underline-offset-4 下划线太靠近文字会显得拥挤,增加偏移量让它更优雅。

8.2 删除线:表示废弃

html
<p>
  原价 <span class="line-through text-gray-400">$99.99</span>
  <span class="text-red-600 font-bold">$49.99</span>
</p>

<del class="line-through text-gray-500">这个 API 已废弃</del>

8.3 波浪线:标记错误

html
<span class="underline decoration-wavy decoration-red-500">
  teh
</span>
<!-- 拼写错误标记 -->

9. 文本溢出:优雅处理长内容

博客列表页经常需要显示文章摘要,但空间有限。

9.1 单行截断

html
<div class="w-64">
  <h3 class="truncate font-semibold">
    深入理解 JavaScript 闭包:从原理到实践的完整指南
  </h3>
</div>

truncate 是一个组合类,等同于:

css
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

9.2 多行截断

html
<div class="w-64">
  <p class="line-clamp-3 text-gray-600">
    闭包是 JavaScript 中最强大也最容易被误解的概念之一。
    本文将从根本原理出发,通过大量实例,帮助你真正理解闭包
    是什么、为什么需要它、以及如何在实际项目中正确使用它。
    我们还会讨论闭包的常见陷阱和性能考量。
  </p>
</div>

line-clamp-3 限制最多显示 3 行,超出部分显示省略号。

9.3 文章卡片实战

把截断应用到博客卡片:

html
<article class="border rounded-lg p-4 w-80">
  <img src="cover.jpg" class="w-full h-40 object-cover rounded" />
  
  <h3 class="mt-4 font-bold text-lg truncate">
    深入理解 JavaScript 闭包:从原理到实践的完整指南
  </h3>
  
  <p class="mt-2 text-gray-600 text-sm line-clamp-2">
    闭包是 JavaScript 中最强大也最容易被误解的概念之一。
    本文将从根本原理出发,帮助你真正理解闭包。
  </p>
  
  <div class="mt-4 flex items-center justify-between text-xs text-gray-500">
    <span>2024年1月15日</span>
    <span>8 分钟阅读</span>
  </div>
</article>

10. 列表样式

技术文章少不了列表。

10.1 无序列表

html
<ul class="list-disc pl-5 space-y-2">
  <li>闭包可以访问外部函数的变量</li>
  <li>闭包在外部函数返回后仍然有效</li>
  <li>每次调用外部函数都创建新的闭包</li>
</ul>

10.2 有序列表(步骤)

html
<ol class="list-decimal pl-5 space-y-4">
  <li>
    <span class="font-semibold">定义外部函数</span>
    <p class="text-gray-600 mt-1">外部函数包含要被闭包访问的变量。</p>
  </li>
  <li>
    <span class="font-semibold">返回内部函数</span>
    <p class="text-gray-600 mt-1">内部函数引用外部函数的变量。</p>
  </li>
  <li>
    <span class="font-semibold">调用返回的函数</span>
    <p class="text-gray-600 mt-1">此时闭包就形成了。</p>
  </li>
</ol>

10.3 自定义列表标记颜色

html
<ul class="list-disc pl-5 marker:text-blue-500">
  <li>蓝色标记的列表项</li>
  <li>更醒目的视觉效果</li>
</ul>

11. 完整的博客文章模板

现在,让我们把所有学到的排版知识整合起来:

html
<article class="max-w-prose mx-auto px-4 py-8 font-sans">
  <!-- 文章头部 -->
  <header class="mb-8">
    <div class="text-sm text-gray-500 mb-2">
      <span class="uppercase tracking-wider">技术深度</span>
    </div>
    <h1 class="text-3xl md:text-4xl font-bold tracking-tight leading-tight">
      深入理解 JavaScript 闭包
    </h1>
    <p class="mt-4 text-lg text-gray-600 leading-relaxed">
      闭包是 JavaScript 中最强大也最容易被误解的概念之一。
      本文将从根本原理出发,帮助你真正理解闭包。
    </p>
    <div class="mt-4 flex items-center gap-4 text-sm text-gray-500">
      <span>张三</span>
      <span>·</span>
      <time>2024年1月15日</time>
      <span>·</span>
      <span>8 分钟阅读</span>
    </div>
  </header>
  
  <!-- 文章正文 -->
  <div class="text-gray-800 leading-relaxed space-y-6">
    <h2 class="text-2xl font-semibold mt-12 mb-4">什么是闭包?</h2>
    <p>
      简单来说,闭包是指一个函数能够记住并访问它的
      <span class="font-semibold">词法作用域</span>,
      即使这个函数在其词法作用域之外执行。
    </p>
    
    <blockquote class="border-l-4 border-gray-300 pl-4 italic text-gray-600">
      <p>"闭包就是函数和其周围状态的组合。"</p>
      <cite class="block mt-2 text-sm not-italic">—— MDN Web Docs</cite>
    </blockquote>
    
    <h3 class="text-xl font-semibold mt-8 mb-4">为什么需要闭包?</h3>
    <p>
      闭包在实际开发中有很多用途:
    </p>
    <ul class="list-disc pl-5 space-y-2">
      <li>数据私有化和封装</li>
      <li>创建工厂函数</li>
      <li>实现柯里化和偏函数应用</li>
      <li>在回调和事件处理中保持状态</li>
    </ul>
    
    <h3 class="text-xl font-semibold mt-8 mb-4">代码示例</h3>
    <p>
      让我们看一个简单的例子。使用
      <code class="bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-pink-600">createCounter</code>
      函数创建计数器:
    </p>
    <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
<code class="font-mono text-sm">function createCounter() {
  let count = 0;
  return function() {
    count += 1;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2</code></pre>
  </div>
  
  <!-- 文章底部 -->
  <footer class="mt-12 pt-8 border-t border-gray-200">
    <div class="flex flex-wrap gap-2">
      <span class="px-3 py-1 bg-gray-100 rounded-full text-sm text-gray-700">
        JavaScript
      </span>
      <span class="px-3 py-1 bg-gray-100 rounded-full text-sm text-gray-700">
        闭包
      </span>
      <span class="px-3 py-1 bg-gray-100 rounded-full text-sm text-gray-700">
        函数式编程
      </span>
    </div>
  </footer>
</article>

12. 小结

本章通过构建一个博客文章页面,系统学习了 UnoCSS 的排版能力。

字体家族的选择确立了界面的基本调性。font-sans 适合大多数界面,font-serif 适合正式文档,font-mono 专为代码设计。通过主题配置可以扩展自定义字体。

字号层次是信息架构的视觉表达。从 text-4xl 的大标题到 text-sm 的辅助信息,每一级都有其适用场景。响应式变体让排版能适应不同屏幕尺寸。

字重区分了标题和正文,font-bold 用于主标题,font-semibold 用于小标题和强调,font-normal 用于正文。记住:不是所有地方都要加粗。

行高对阅读舒适度至关重要。正文使用 leading-relaxed,标题可以用 leading-tight。行宽越宽,行高应该越大。

字间距是精细调节工具。大标题使用 tracking-tight 收紧,全大写文本使用 tracking-wider 加宽。

文本截断解决了有限空间的内容展示问题。truncate 用于单行,line-clamp-* 用于多行。

好的排版是隐形的。 当读者能够沉浸在内容中,完全不会注意到字体、行高、间距这些"技术细节"时,排版就成功了。

下一章我们将学习阴影、滤镜和过渡效果,为界面增添更多质感和动态感。

注入灵魂:文本、字体与排版 has loaded