Skip to content

构建骨架:布局、尺寸与间距

在前面的章节中,我们掌握了 UnoCSS 的核心概念——规则如何将类名映射为 CSS,预设如何组织这些规则,变体如何修饰生成的选择器。那些是"原理",从本章开始,我们要解决"实战"问题。

首先要问一个问题:构建一个页面,最先需要解决什么?

答案是:骨架。不管是简单的落地页还是复杂的后台系统,第一步都是把页面的结构搭起来——导航在哪里、内容区怎么划分、侧边栏占多宽、元素之间留多少空隙。这些问题的答案,决定了页面的基本形态。

本章将通过构建一个后台管理系统的布局来学习 UnoCSS 的布局、尺寸和间距工具类。不是罗列所有可用的类名,而是在解决实际问题的过程中,自然地引入需要的工具。


1. 从一个真实的布局需求开始

假设你接到一个任务:搭建一个后台管理系统的基础布局框架。产品经理给出的需求是这样的:

顶部有一个固定的导航栏,高度 64px,始终显示在页面顶部。左侧有一个侧边栏,宽度 240px,可以折叠。右侧是主内容区域,占据剩余空间。整个页面高度填满视口,内容区域超出时可滚动。

思考一下,如果用传统 CSS 实现这个布局,你会怎么写?

你可能会写类似这样的代码:

css
.layout {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.header {
  height: 64px;
  flex-shrink: 0;
}
.body {
  display: flex;
  flex: 1;
  overflow: hidden;
}
.sidebar {
  width: 240px;
  flex-shrink: 0;
}
.main {
  flex: 1;
  overflow-y: auto;
}

这段 CSS 虽然不复杂,但你需要在 CSS 文件和 HTML 文件之间来回切换,还要想一堆类名。

使用 UnoCSS,同样的布局可以直接在 HTML 中完成:

html
<div class="flex flex-col h-screen">
  <!-- 顶部导航栏 -->
  <header class="h-16 shrink-0 bg-white border-b">
    导航栏
  </header>
  
  <!-- 主体区域 -->
  <div class="flex flex-1 overflow-hidden">
    <!-- 侧边栏 -->
    <aside class="w-60 shrink-0 bg-gray-50 border-r overflow-y-auto">
      侧边栏菜单
    </aside>
    
    <!-- 主内容区 -->
    <main class="flex-1 overflow-y-auto p-6">
      主要内容
    </main>
  </div>
</div>

有没有感觉到不同? 你不需要写任何 CSS 文件,不需要想类名,布局的意图直接体现在 HTML 中。h-screen 告诉你这个容器占满视口高度,flex flex-col 告诉你它是一个垂直排列的弹性容器,shrink-0 告诉你这个元素不会被压缩。

接下来,让我们逐个拆解这个布局用到的工具类,理解它们解决了什么问题。


2. Flexbox:一维布局的首选方案

2.1 什么时候用 Flexbox?

现在要问一个问题:什么场景下应该选择 Flexbox?

答案是:当你需要在一个方向上(水平或垂直)排列和分布元素时

比如导航栏中的菜单项水平排列,侧边栏中的菜单项垂直排列,按钮组中的按钮水平排列并均匀分布,卡片内的标题、内容、按钮垂直排列。

Flexbox 的核心思想是:容器控制子元素的排列方式,子元素可以伸缩以适应可用空间。

2.2 开启 Flex 容器

回到我们的布局代码,第一行就是 flex flex-col

html
<div class="flex flex-col h-screen">

flex 将这个 <div> 变成一个弹性容器。它的子元素(header 和主体区域)会按照弹性布局的规则排列。

flex-col 指定排列方向是垂直的(从上到下)。如果不加这个类,默认是水平排列(从左到右)。

思考一下:为什么要用垂直排列? 因为我们的布局是"上面是导航栏,下面是主体"这样的结构。

如果你遇到的是水平排列的场景,比如导航栏中的菜单项:

html
<nav class="flex">
  <a href="/">首页</a>
  <a href="/products">产品</a>
  <a href="/about">关于我们</a>
</nav>

不需要 flex-row,因为这是默认行为。只有当你需要反向排列时(从右到左或从下到上),才需要明确指定 flex-row-reverseflex-col-reverse

2.3 控制子元素如何伸缩

在主体区域的代码中,你会看到 flex-1

html
<div class="flex flex-1 overflow-hidden">

这个 flex-1 解决了什么问题?

我们希望主体区域占据导航栏之外的所有剩余空间。flex-1 正是用来做这件事的——它让元素增长以填满可用空间。

与之对应的是 shrink-0

html
<header class="h-16 shrink-0 bg-white border-b">

shrink-0 告诉导航栏:不管空间多紧张,都不要压缩我。这保证了导航栏始终是 64px 高,不会因为内容区域太多而被挤压。

这里有一个常见的踩坑场景:你设置了一个元素的固定宽度或高度,但在 Flex 容器中它却被压缩了。原因是 Flex 子元素默认 flex-shrink: 1,会在空间不足时收缩。解决方法就是加上 shrink-0

再看侧边栏的代码:

html
<aside class="w-60 shrink-0 bg-gray-50 border-r overflow-y-auto">

同样使用了 shrink-0,确保侧边栏始终保持 240px(w-60 = 15rem = 240px)的宽度。

而主内容区用的是 flex-1

html
<main class="flex-1 overflow-y-auto p-6">

它会自动填满侧边栏之外的所有空间。当你调整浏览器窗口大小时,只有主内容区的宽度会变化。

2.4 对齐方式:justify 和 items

现在思考另一个场景:导航栏内部的布局

假设导航栏左边是 Logo,右边是用户头像,中间是空白。这是一个典型的"两端对齐"需求:

html
<header class="flex justify-between items-center h-16 px-6 bg-white border-b">
  <div class="text-xl font-bold">Logo</div>
  <div class="flex items-center gap-4">
    <button>通知</button>
    <img class="w-8 h-8 rounded-full" src="avatar.jpg" alt="头像" />
  </div>
</header>

justify-between 将子元素分散到主轴(水平方向)的两端,Logo 靠左,用户区域靠右。

items-center 让子元素在交叉轴(垂直方向)上居中对齐。没有这个类,元素会默认拉伸以填满容器高度。

这里有一个窍门justify-* 控制主轴(排列方向),items-* 控制交叉轴(垂直于排列方向)。如果你分不清哪个是哪个,就记住:justify 控制 content 沿排列方向的分布,items 控制 content 在另一个方向的对齐。

另一个常见需求是完美居中

html
<div class="flex justify-center items-center h-screen">
  <div class="text-2xl">登录框</div>
</div>

justify-center + items-center + 容器有明确高度 = 水平垂直都居中。这可能是最常用的居中方案。

2.5 Flex 布局的常见模式

让我总结几个用 Flexbox 解决的典型场景:

场景一:导航栏

html
<nav class="flex justify-between items-center h-16 px-6">
  <div>Logo</div>
  <div class="flex gap-6">
    <a href="#">菜单1</a>
    <a href="#">菜单2</a>
  </div>
</nav>

场景二:卡片内容垂直排列,按钮固定在底部

html
<div class="flex flex-col h-64 p-4 bg-white rounded-lg shadow">
  <h3 class="font-bold">标题</h3>
  <p class="flex-1 text-gray-600">描述内容...</p>
  <button class="mt-auto">操作按钮</button>
</div>

mt-auto 是一个很巧妙的技巧——它让按钮的上边距自动扩展,从而把按钮推到底部。

场景三:输入框组

html
<div class="flex">
  <span class="px-3 py-2 bg-gray-100 border border-r-0 rounded-l">https://</span>
  <input class="flex-1 px-3 py-2 border rounded-r" placeholder="域名" />
</div>

前缀固定宽度,输入框占据剩余空间。


3. Grid:二维布局的利器

3.1 什么时候用 Grid?

Flexbox 解决一维布局,Grid 解决二维布局

什么是二维布局?当你需要同时控制行和列的时候。比如:卡片网格(3列x多行),表格型数据展示,复杂的表单布局(标签在左边,输入框在右边),仪表盘的多区块布局。

3.2 卡片网格:最常见的 Grid 场景

假设你要实现一个商品列表页,需要在不同屏幕上显示不同数量的卡片列:

html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
  <div class="bg-white rounded-lg shadow p-4">商品卡片 1</div>
  <div class="bg-white rounded-lg shadow p-4">商品卡片 2</div>
  <div class="bg-white rounded-lg shadow p-4">商品卡片 3</div>
  <div class="bg-white rounded-lg shadow p-4">商品卡片 4</div>
  <!-- 更多卡片... -->
</div>

这段代码做了什么?

grid 将容器设为网格布局。grid-cols-1 在移动端默认单列显示。md:grid-cols-2 在中等屏幕(≥768px)显示两列。lg:grid-cols-3 在大屏幕(≥1024px)显示三列。xl:grid-cols-4 在超大屏幕(≥1280px)显示四列。gap-6 设置网格项之间的间距为 1.5rem。

思考一下:同样的效果用 Flexbox 能实现吗?

可以,但会更复杂:

html
<div class="flex flex-wrap gap-6">
  <div class="w-full md:w-[calc(50%-0.75rem)] lg:w-[calc(33.333%-1rem)] xl:w-[calc(25%-1.125rem)]">
    卡片
  </div>
</div>

你需要计算每个卡片的宽度,还要考虑 gap 的影响。用 Grid,列数的概念更直观,不需要手动计算宽度。

3.3 复杂的网格布局

有时候你需要某些元素跨越多列或多行。比如后台仪表盘的布局:

html
<div class="grid grid-cols-4 gap-6">
  <!-- 欢迎横幅,跨越所有列 -->
  <div class="col-span-4 p-6 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg">
    欢迎回来,管理员!
  </div>
  
  <!-- 数据统计卡片,各占一列 -->
  <div class="p-4 bg-white rounded-lg shadow">今日订单:128</div>
  <div class="p-4 bg-white rounded-lg shadow">今日收入:¥12,800</div>
  <div class="p-4 bg-white rounded-lg shadow">新增用户:56</div>
  <div class="p-4 bg-white rounded-lg shadow">转化率:3.2%</div>
  
  <!-- 图表区域,跨越3列 -->
  <div class="col-span-3 p-6 bg-white rounded-lg shadow h-80">
    销售趋势图表
  </div>
  
  <!-- 待办事项,跨越1列 -->
  <div class="p-6 bg-white rounded-lg shadow">
    待办事项列表
  </div>
</div>

col-span-4 让元素跨越4列(即所有列)。col-span-3 让元素跨越3列。没有指定 col-span 的元素默认占1列。

3.4 不等宽列布局

如果需要列宽不等,比如"200px 侧边栏 + 自适应主内容":

html
<div class="grid grid-cols-[200px_1fr] gap-6">
  <aside class="p-4 bg-gray-100">固定200px</aside>
  <main class="p-4">自动填充剩余空间</main>
</div>

方括号内是任意 CSS 值,1fr 表示占据1份可用空间。

更复杂的例子:"100px + 自适应 + 自适应 + 200px":

html
<div class="grid grid-cols-[100px_1fr_1fr_200px]">
  <!-- 四列布局 -->
</div>

4. 尺寸控制:宽度和高度

4.1 固定尺寸 vs 相对尺寸

在我们的布局中出现了 h-16w-60h-screen 这些尺寸类。它们分别代表什么?

h-16 是固定高度,等于 4rem = 64px。w-60 是固定宽度,等于 15rem = 240px。h-screen 是相对于视口的高度,等于 100vh。

什么时候用固定尺寸,什么时候用相对尺寸?

固定尺寸适用于设计稿明确指定像素值的场景,如导航栏高度 64px,侧边栏宽度 240px,头像大小 32x32px。

相对尺寸适用于需要适应容器或视口的场景,如 w-full(100%宽度),h-screen(100%视口高度),w-1/2(50%宽度)。

4.2 UnoCSS 的尺寸刻度

UnoCSS 的尺寸值遵循一个系统:数字乘以 0.25rem(4px)。所以 h-16 = 16 × 0.25rem = 4rem = 64px。

常用的尺寸值对照:4 = 1rem = 16px,8 = 2rem = 32px,12 = 3rem = 48px,16 = 4rem = 64px,20 = 5rem = 80px。

记住这个规律后,你就能快速换算任何尺寸。

4.3 实用的尺寸技巧

技巧一:正方形元素用 size-*

html
<img class="size-12 rounded-full" src="avatar.jpg" />

size-12 等于 w-12 h-12,创建一个 48x48px 的正方形。

技巧二:限制最大宽度改善阅读体验

html
<article class="max-w-prose mx-auto">
  <!-- 文章内容 -->
</article>

max-w-prose 大约是 65 个字符宽,这是阅读舒适度的黄金宽度。mx-auto 让文章居中。

技巧三:响应式宽度

html
<div class="w-full md:w-1/2 lg:w-1/3">
  响应式卡片
</div>

移动端占满宽度,中等屏幕占一半,大屏幕占三分之一。


5. 间距系统:让界面"呼吸"

5.1 间距的重要性

好的间距让界面更易读、更专业。 间距过密让人感觉拥挤,间距过大让内容显得松散。

在我们的布局代码中,p-6 给主内容区添加了内边距:

html
<main class="flex-1 overflow-y-auto p-6">

这确保内容不会紧贴容器边缘。

5.2 Margin vs Padding vs Gap

什么时候用 margin(外边距),什么时候用 padding(内边距),什么时候用 gap(间距)?

Padding 用于元素内部:卡片的内边距,按钮的内边距,容器与内容的距离。

html
<div class="p-6 bg-white rounded-lg">
  卡片内容与边框保持距离
</div>

Margin 用于元素之间:段落之间的距离,标题与正文的距离。

html
<h1 class="mb-4">标题</h1>
<p class="mb-2">段落1</p>
<p class="mb-2">段落2</p>

Gap 用于布局容器的子元素:Flex 或 Grid 容器内子元素的间距。

html
<div class="flex gap-4">
  <button>按钮1</button>
  <button>按钮2</button>
</div>

为什么 Gap 比 Margin 更适合布局? 因为 Gap 只影响元素之间的距离,不会给第一个和最后一个元素添加多余的边距。

5.3 间距的命名规则

UnoCSS 的间距类遵循简单的模式:{属性}-{方向}-{值}

属性有 m(margin)和 p(padding)。方向有 t(top)、r(right)、b(bottom)、l(left)、x(水平)、y(垂直)。省略方向表示所有方向。

值与尺寸系统一致,乘以 0.25rem。

html
<div class="mt-4">      <!-- margin-top: 1rem -->
<div class="px-6">      <!-- padding-left: 1.5rem; padding-right: 1.5rem -->
<div class="py-2">      <!-- padding-top: 0.5rem; padding-bottom: 0.5rem -->
<div class="m-4">       <!-- margin: 1rem (所有方向) -->

5.4 一个间距的实战例子

设计一个卡片组件:

html
<div class="p-6 bg-white rounded-lg shadow">
  <h3 class="text-lg font-bold mb-2">卡片标题</h3>
  <p class="text-gray-600 mb-4">这是卡片的描述内容,可以有多行文字。</p>
  <div class="flex gap-2">
    <button class="px-4 py-2 bg-blue-500 text-white rounded">主要操作</button>
    <button class="px-4 py-2 bg-gray-200 rounded">次要操作</button>
  </div>
</div>

p-6 给卡片整体留出呼吸空间。mb-2 让标题和描述之间有适当距离。mb-4 让描述和按钮区之间有更大距离(视觉分组)。gap-2 让两个按钮之间有间隙。px-4 py-2 给按钮设置水平和垂直的内边距。


6. 回顾我们的布局

现在让我们回顾一下完整的后台布局代码,你应该能理解每个类的作用了:

html
<div class="flex flex-col h-screen">
  <!-- 垂直弹性容器,占满视口高度 -->
  
  <header class="flex justify-between items-center h-16 shrink-0 px-6 bg-white border-b">
    <!-- 导航栏:水平弹性布局,两端对齐,垂直居中 -->
    <!-- 高度64px,不收缩,有左右内边距 -->
    <div class="text-xl font-bold">Logo</div>
    <div class="flex items-center gap-4">
      <button>通知</button>
      <img class="size-8 rounded-full" src="avatar.jpg" />
    </div>
  </header>
  
  <div class="flex flex-1 overflow-hidden">
    <!-- 主体区域:水平弹性布局,填满剩余高度,内容溢出隐藏 -->
    
    <aside class="w-60 shrink-0 p-4 bg-gray-50 border-r overflow-y-auto">
      <!-- 侧边栏:固定240px宽,不收缩,有内边距,可滚动 -->
      <nav class="flex flex-col gap-2">
        <a class="px-4 py-2 rounded hover:bg-gray-200">仪表盘</a>
        <a class="px-4 py-2 rounded hover:bg-gray-200">用户管理</a>
        <a class="px-4 py-2 rounded hover:bg-gray-200">订单管理</a>
      </nav>
    </aside>
    
    <main class="flex-1 p-6 overflow-y-auto">
      <!-- 主内容区:填满剩余宽度,有内边距,可滚动 -->
      <h1 class="text-2xl font-bold mb-6">仪表盘</h1>
      
      <!-- 数据统计卡片网格 -->
      <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
        <div class="p-6 bg-white rounded-lg shadow">
          <div class="text-gray-500 text-sm">今日订单</div>
          <div class="text-3xl font-bold">128</div>
        </div>
        <!-- 更多统计卡片... -->
      </div>
    </main>
  </div>
</div>

7. 小结

本章我们通过构建一个后台管理系统的布局,学习了 UnoCSS 的布局、尺寸和间距工具类。

Flexbox 是一维布局的首选。flex 开启容器,flex-col 垂直排列,justify-* 控制主轴对齐,items-* 控制交叉轴对齐,flex-1 让元素伸展,shrink-0 防止元素被压缩。

Grid 适合二维布局。grid-cols-* 定义列数,gap-* 设置间距,col-span-* 让元素跨列。响应式网格用 md:grid-cols-* 这样的变体实现。

尺寸w-*h-* 控制。数字乘以 0.25rem 得到实际值。size-* 创建正方形,分数值创建百分比宽度,max-w-* 限制最大宽度。

间距是界面的呼吸空间。p-* 是内边距,m-* 是外边距,gap-* 是布局间距。方向后缀 t/r/b/l/x/y 控制单独方向。

这些工具类组合起来,足以应对绝大多数的布局需求。下一章我们将学习如何为这些骨架填充色彩——背景、边框与渐变。


8. 动手练习

练习1:用本章学到的知识,实现一个"媒体对象"布局:左边是头像(圆形,48x48px),右边是用户名和评论内容,用户名加粗,评论内容灰色。

练习2:实现一个响应式的产品卡片网格。要求:移动端1列,平板2列,桌面端3列,超大屏4列。卡片包含图片、标题、价格,有阴影和圆角。

练习3:实现一个"粘性页脚"布局。即使内容很少,页脚也始终在页面底部;内容很多时,页脚在内容下方。

构建骨架:布局、尺寸与间距 has loaded