普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月20日首页

CSS 相对颜色:告别 180 个颜色变量的设计系统噩梦

作者 大知闲闲i
2025年11月19日 15:51

当组件库中的颜色变量达到 180 个时,一次品牌色变更就成了前端开发的噩梦。CSS 相对颜色语法将彻底改变这一现状。

一个让人沉默的现实

最近在排查一个组件库的主题 BUG 时,我们发现了令人震惊的事实:这个看似成熟的设计系统中,竟然定义了 180 个颜色变量

更可怕的是,每次品牌主色调整,都需要在 3 个不同的文件中同步修改 15 种深浅变化、hover 状态、透明度变体……设计同学轻描淡写的一句"主色想从偏蓝调成更紫一点",意味着工程侧需要:

  • 手动修改 15+ 个变量

  • 反复对比 hover、active 状态是否协调

  • 仔细检查半透明背景是否漏改

  • 确保整个颜色体系保持和谐

漏改一个变量,hover 状态显得怪异;漏改两个,整套主题就开始"发脏"。

传统颜色系统的困境

当前绝大多数设计系统的配色方案可以概括为:靠人肉复制的"颜色农场"

:root {
  /* 主色系 */
  --color-primary: #3b82f6;
  --color-primary-hover: #2563eb;
  --color-primary-active: #1d4ed8;
  --color-primary-light: #93c5fd;
  --color-primary-dark: #1e40af;
  
  /* 辅助色系 */
  --color-secondary: #8b5cf6;
  --color-secondary-hover: #7c3aed;
  --color-secondary-active: #6d28d9;
  
  /* 继续衍生... */
}

这种模式的痛点显而易见:

  • 维护成本高:一个主色系需要十几二十个变量

  • 同步困难:多处定义的变量容易遗漏

  • 不可靠:手动调色依赖个人感觉,缺乏系统性

CSS 相对颜色:革命性的解决方案

CSS 相对颜色语法引入的 from 关键字,让颜色从"死值"变成"活公式"。

基础语法

color-function(from origin-color channel1 channel2 channel3 / alpha)

拆解说明:

  • color-function:输出格式,如 rgb()hsl()oklch()

  • from:关键字符,声明颜色来源

  • origin-color:基准颜色,支持 hex、RGB、HSL 等格式

  • channel1 ~ 3:可访问和修改的通道值

  • alpha:可选透明度通道

实际应用示例

:root {
  --primary: #3b82f6;
}

.button {
  background: var(--primary);
}

.button:hover {
  /* 基于主色自动计算 hover 状态 */
  background: hsl(from var(--primary) h s calc(l - 10));
}

这一行 hsl(from ...) 的改变,将 hover 效果从"写死"变成了"相对基色、自动联动"。从此,品牌色只需修改一个 --primary 变量,所有衍生状态自动跟随。

from 关键字的魔力

from 的核心作用是将颜色分解为通道值,让我们能够像搭乐高一样重新组合:

/* 将绿色分解为 RGB 通道 */
rgb(from green r g b)  /* 输出: rgb(0 128 0) */

/* 用绿色通道创建灰度 */
rgb(from green g g g)  /* 输出: rgb(128 128 128) */

/* 随意调换通道顺序 */
rgb(from green b r g)  /* 输出: rgb(0 0 128) */

跨色彩空间转换

from 自动处理色彩空间转换,让颜色格式不再成为障碍:

/* RGB 转 HSL */
hsl(from rgb(255 0 0) h s l)

/* Hex 转 OKLCH */
oklch(from #3b82f6 l c h)

这对设计系统意义重大:源头存储格式不再重要,使用端始终使用统一的可计算空间。

calc():颜色计算的引擎

真正的威力在于将 calc() 与颜色通道结合:

/* 变亮:提高亮度 */
hsl(from blue h s calc(l + 20))

/* 变暗:降低亮度 */  
hsl(from blue h s calc(l - 20))

/* 半透明:调整透明度 */
rgb(from blue r g b / calc(alpha * 0.5))

/* 调色:旋转色相 */
hsl(from blue calc(h + 180) s l)

大部分颜色衍生逻辑都可以归结为:通道 + 偏移量通道 × 系数

OKLCH:更智能的色彩空间

虽然 HSL 很流行,但它有个致命缺陷:亮度感知不均

hsl(220 80% 50%)  /* 蓝色 */
hsl(120 80% 50%)  /* 绿色 */

理论上两者亮度相同,但人眼感知中绿色明显更亮。OKLCH 解决了这个问题:

  • L(Lightness):0-1,感知亮度,更符合人眼

  • C(Chroma):0-约0.37,颜色纯度

  • H(Hue):0-360,色相角度

    oklch(0.55 0.15 260) /* 蓝色 / oklch(0.55 0.15 140) / 绿色 */

在 OKLCH 中,相同的 L 值在不同色相间具有一致的亮度感知,这让程序化调色更加可靠。

构建智能颜色系统

第一步:定义品牌基色

:root {
  /* 只用定义 4 个基础品牌色 */
  --brand-primary: oklch(0.55 0.2 265);
  --brand-success: oklch(0.65 0.18 145);
  --brand-error: oklch(0.6 0.25 25);
  --brand-warning: oklch(0.75 0.15 85);
}

第二步:按规则生成完整色板

:root {
  /* Primary 色系 - 全部从基色派生 */
  --primary: var(--brand-primary);
  --primary-hover: oklch(from var(--brand-primary) calc(l - 0.1) c h);
  --primary-active: oklch(from var(--brand-primary) calc(l - 0.15) c h);
  --primary-light: oklch(from var(--brand-primary) calc(l + 0.2) calc(c * 0.5) h);
  --primary-lighter: oklch(from var(--brand-primary) calc(l + 0.3) calc(c * 0.3) h);
  --primary-alpha-10: oklch(from var(--brand-primary) l c h / 0.1);
  --primary-alpha-20: oklch(from var(--brand-primary) l c h / 0.2);
  
  /* 其他色系采用相同模式 */
  --success: var(--brand-success);
  --success-hover: oklch(from var(--brand-success) calc(l - 0.1) c h);
  --success-light: oklch(from var(--brand-success) calc(l + 0.2) calc(c * 0.5) h);
}

四个基色变量,扩展出完整的颜色体系。品牌色调整时,只需修改四个基础值。

暗色模式的革命

传统暗色模式需要维护两套 token,现在只需一个公式:

:root {
  --surface: oklch(0.98 0.02 240);
  --text: oklch(0.25 0.03 240);
}

[data-theme="dark"] {
  /* 亮度反转实现暗色模式 */
  --surface: oklch(from var(--surface) calc(1 - l) c h);
  --text: oklch(from var(--text) calc(1 - l) c h);
}

实战高级技巧

1. 智能阴影系统

.card {
  --card-bg: var(--primary);
  background: var(--card-bg);
  box-shadow: 
    0 4px 6px oklch(from var(--card-bg) l c h / 0.2),
    0 10px 15px oklch(from var(--card-bg) l c h / 0.15);
}

阴影自动适应背景色,主题切换时自然过渡。

2. 确保可读性的文本颜色

.tag {
  --tag-bg: var(--primary);
  background: var(--tag-bg);
  /* 文本亮度比背景高 0.6,确保对比度 */
  color: oklch(from var(--tag-bg) calc(l + 0.6) c h);
}

3. 品牌化半透明遮罩

.modal-backdrop {
  background: oklch(from var(--brand-primary) l c h / 0.7);
}

浏览器支持与渐进增强

截至 2025 年,相对颜色已获得良好支持:

  • Chrome 119+ ✅

  • Firefox 128+ ✅

  • Safari 16.4+ ✅

  • Edge 119+ ✅

覆盖率约 83%,对于不支持的环境可提供静态回退:

.button {
  background: #2563eb; /* 回退值 */
  background: oklch(from var(--primary) calc(l - 0.1) c h);
}

避坑指南

  1. 避免过深派生链:从基色直接推导,最多两层

  2. 控制 Chroma 范围:OKLCH 中 Chroma 超过 0.37 可能导致颜色溢出

  3. 正确使用 Alphaoklch(0.6 0.2 265 / 0.5) 而非 oklch(0.6 0.2 265 0.5)

总结:从小升级到大变革

CSS 相对颜色解决的不仅是技术问题,更是设计系统维护的哲学变革:

  • 主题切换不再是灾难:改一个变量,全站自洽

  • 颜色 Token 真正集中管理:从分散定义到统一源头

  • 设计规则化:深浅、状态、透明度都成为可复用的公式

  • 开发体验提升:从机械调色到智能推导

下一次重构配色系统时,不妨尝试将基准色、状态色、暗色模式、半透明层全部交给相对颜色计算。那种"改一处,全局联动"的流畅体验,确实让人上头。

从 180 个颜色变量到 4 个基色变量,这不只是数量的减少,更是设计系统维护理念的质的飞跃。

Gird快速入门(手把手敲demo)

作者 新晨437
2025年11月19日 21:02

Gird 布局是 CSS 中目前最强大、最成熟的二维布局方案,它可以让您轻松创建复杂的网页结构。

这份快速入门指南将带您从零开始,快速掌握 Grid 的核心概念和用法。

1. 什么是 CSS Grid?

CSS Grid Layout(网格布局)是一个二维的布局系统,意味着它可以同时处理。这与 Flexbox(一维布局,主要处理行列)形成鲜明对比。

核心思想:将一个容器划分为一个个网格,你可以将子元素放置在这些网格的任意位置,甚至可以创建复杂的、不对称的布局。


2. 基本概念

在开始之前,先了解两个核心概念:

  • Grid Container(网格容器):应用了 display: grid; 的元素。它的所有直接子元素将成为 Grid Item。
  • Grid Item(网格项目):Grid Container 的直接子元素
<div class="container"> <!-- Grid Container -->
  <div class="item">1</div> <!-- Grid Item -->
  <div class="item">2</div> <!-- Grid Item -->
  <div class="item">3</div> <!-- Grid Item -->
</div>

3. 快速启动:创建一个简单的网格

让我们通过一个简单的例子,一步步创建一个 3x3 的网格。

步骤 1:定义网格容器

首先,将一个元素的 display 属性设置为 gridinline-grid

.container {
  display: grid;
}

现在,.container 就成为了一个网格容器,它的直接子元素自动成为了网格项目。

步骤 2:划分行和列(定义网格轨道)

这是 Grid 布局的核心。我们使用 grid-template-columnsgrid-template-rows 属性来定义网格的结构。

  • grid-template-columns:定义每一列的宽度。
  • grid-template-rows:定义每一行的高度。

让我们定义一个 3 列(每列 100px)和 3 行(每行 100px)的网格:

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px; /* 三列,每列100px */
  grid-template-rows: 100px 100px 100px;    /* 三行,每行100px */
}

更现代的写法:使用 repeat() 函数 当列或行很多时,repeat() 函数非常有用。

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px); /* 等同于 100px 100px 100px */
  grid-template-rows: repeat(3, 100px);
}

灵活的写法:使用 fr 单位 fr(fraction)单位代表网格容器中的等分空间,它让布局变得非常灵活。

.container {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr; /* 总宽度分为4份,第二列占2份,第一、三列各占1份 */
  grid-template-rows: repeat(3, 100px);
}

混合使用:

.container {
  display: grid;
  grid-template-columns: 200px 1fr 20%; /* 第一列固定200px,第三列是容器的20%,中间列占据剩余空间 */
}

步骤 3:网格间隙(Gap)

使用 gap 属性可以设置网格项目之间的间距。它是 row-gapcolumn-gap 的简写。

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  gap: 10px; /* 行和列的间隙都是10px */
  /* 或者分开写: */
  /* row-gap: 15px; */
  /* column-gap: 10px; */
}

到此为止,一个标准的 3x3 网格就创建好了!你的 HTML 和 CSS 看起来应该是这样的:

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .container {
      display: grid;
      grid-template-columns: repeat(3, 100px);
      grid-template-rows: repeat(3, 100px);
      gap: 10px;
      background-color: #f0f0f0;
      padding: 10px;
    }
    .item {
      background-color: #4CAF50;
      color: white;
      padding: 20px;
      text-align: center;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
    <div class="item">5</div>
    <div class="item">6</div>
    <div class="item">7</div>
    <div class="item">8</div>
    <div class="item">9</div>
  </div>
</body>
</html>

4. 高级操作:放置网格项目

默认情况下,项目会按顺序逐行逐列填充网格。但 Grid 的强大之处在于你可以精确控制每个项目的位置。

我们通过 网格线(Grid Lines) 来定位项目。网格线是划分网格的线,从 1 开始编号(也从 -1 开始倒序编号)。

CSS Grid Lines转存失败,建议直接上传图片文件

使用以下属性来控制项目位置:

  • grid-column-start
  • grid-column-end
  • grid-row-start
  • grid-row-end
  • 简写:grid-columngrid-row

示例:让第一个 item 横跨两列

.item:nth-child(1) {
  grid-column-start: 1;
  grid-column-end: 3; /* 从第1条纵线开始,到第3条纵线结束 */
  /* 简写: */
  /* grid-column: 1 / 3; */
}

示例:创建一个页面的经典布局

<div class="container">
  <header>Header</header>
  <aside>Sidebar</aside>
  <main>Main Content</main>
  <footer>Footer</footer>
</div>
.container {
  display: grid;
  grid-template-columns: 200px 1fr; /* 侧边栏固定宽度,主内容区自适应 */
  grid-template-rows: auto 1fr auto; /* 页头和页脚高度由内容决定,主内容区占据剩余空间 */
  height: 100vh; /* 让容器充满整个视口高度 */
  gap: 10px;
}

header {
  grid-column: 1 / -1; /* 从第1条纵线开始,到最后一条纵线结束(横跨所有列) */
  background: lightblue;
}

aside {
  grid-column: 1 / 2; /* 占据第一列 */
  background: lightcoral;
}

main {
  grid-column: 2 / 3; /* 占据第二列 */
  background: lightgreen;
}

footer {
  grid-column: 1 / -1; /* 横跨所有列 */
  background: lightgray;
}

5. 快速总结与最佳实践

  1. 启动display: grid;
  2. 定义结构
    • grid-template-columns: 定义列。
    • grid-template-rows: 定义行。
    • 善用 repeat()fr 单位。
  3. 设置间距gap
  4. 精细控制项目:使用 grid-columngrid-row 基于网格线进行定位。
  5. 命名网格线:对于复杂布局,可以给网格线起名字,让代码更易读。
    .container {
      grid-template-columns: [sidebar-start] 200px [sidebar-end content-start] 1fr [content-end];
    }
    header {
      grid-column: sidebar-start / content-end;
    }
    

练习建议

打开浏览器的开发者工具,在 ElementsInspector 面板中,找到你的网格容器,旁边会有一个 grid 的标签,点击它可以在页面上可视化你的网格结构。这是学习和调试 Grid 的绝佳方式!

Grid 布局功能非常丰富,还包括网格区域(grid-template-areas)、对齐属性(justify-items, align-content等),但掌握了以上核心内容,你已经可以应对 90% 的布局需求了。快去动手试试吧!

昨天以前首页

Gird布局详解

作者 嬉皮客
2025年11月18日 21:16

一、什么是 Grid 布局?

CSS Grid Layout(网格布局)是 CSS 提供的二维布局系统,可以同时在行(row)和列(column)两个方向上对元素进行排列。
它和 Flexbox 不同,Flexbox 是一维布局(只能按行或列排),而 Grid 是二维布局,可以轻松实现复杂的跨行、跨列布局。

特点:

  • 二维布局:同时控制行和列,对应一维布局复杂度也随之增加
  • 跨行跨列:元素可以跨多个单元格,一维度布局需要层层嵌套来做这些效果
  • 语义清晰grid-template-areas 像地图一样描述布局,布局就像ASCLL表一样清晰直观
  • 响应式友好:只改 Grid 定义,不动 HTML,就能重排布局,经常配合@media使用
  • 现代浏览器支持好:桌面端和移动端几乎全覆盖(IE11 部分支持旧语法),现代浏览器基本都是支持的,排除一下老旧项目(政府、银行等)还在使用

二、基本概念

容器和项目

什么是容器?

设置了 display: grid 或 display: inline-grid 的元素,就是 Grid 容器,容器的直接子元素就是 Grid 项目。容器负责定义整个网格的结构(行、列、间距、对齐方式等)。

什么是项目?

Grid 容器的直接子元素就是 Grid 项目,项目可以通过属性控制它在网格中的位置、跨行跨列、对齐方式等。

行和列

容器里面的水平区域称为"行"(row),垂直区域称为"列"(column)。

下图水平深色区域就是“行”,垂直深色区域就是“列”

image.png

单元格

行和列的交叉区域,称为"单元格"(cell),n行和m列会产生n x m个单元格。比如,3行3列会产生9个单元格。

网格线

划分网格的线,称为"网格线"(grid line)。水平网格线划分出行,垂直网格线划分出列,n行有n + 1根水平网格线,m列有m + 1根垂直网格线,比如三行就有四根水平网格线。

如下图就有5根水平网格线和5根垂直网格线

image.png

三、容器属性

display:grid | inline-grid

作用:指定一个容器采用网格布局

语法:

.container{
    display: grid | inline-grid;
}

注意:设为网格布局以后,容器子元素(项目)的floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-*等设置都将失效。

grid-template-columns与grid-template-rows

作用:grid-template-columns属性定义每一列的列宽,grid-template-rows属性定义每一行的行高

实例:指定了一个三行三列的网格,列宽和行高都是100px

image.png

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
<div class="container">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
</div>
repeat()

作用:使用repeat()函数,简化重复的值,接受两个参数,第一个参数是重复的次数,第二个参数是所要重复的值,也可以重复某个模式

实例:

  • 简单重复值

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
}
.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • 重复某个模式

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
}
.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
auto-fill与auto-fit

作用:单元格的大小是固定的,但是容器的大小不确定。如果希望每一行(或每一列)容纳尽可能多的单元格,这时可以使用auto-fill关键字表示自动填充,只有当容器足够宽,可以在一行容纳所有单元格,并且单元格宽度不固定的时候,才会有差异:auto-fill会用空格子填满剩余宽度,auto-fit则会尽量扩大单元格的宽度

实例:

例子中宽设置了400px,高设置200px,属性auto-fill,高400px/100px=4个格子,宽200px/100px=2个格子,所以超出的第九个被空放了

image.png

.container {
  width: 400px;
  height: 200px;
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px);
  grid-template-rows: repeat(auto-fill, 100px);
}
fr

作用:为了方便表示比例关系,网格布局提供了fr关键字(fraction 的缩写,意为"片段")。如果两列的宽度分别为1fr2fr,就表示后者是前者的两倍

实例:

image.png

.container {
  width: 400px;
  display: grid;
  grid-template-columns: 300px 1fr 2fr;
  grid-template-rows: repeat(3, 100px);
}
minmax

作用:minmax()函数产生一个长度范围,表示长度就在这个范围之中。它接受两个参数,分别为最小值和最大值

实例:

每行第三列位置宽度100px~2fr

image.png

.container {
  display: grid;
  grid-template-columns: 1fr 1fr minmax(100px, 2fr);
  grid-template-rows: repeat(3, 100px);
}
auto

作用:由浏览器自己决定长度

实例:

image.png

.container {
  width: 400px;
  display: grid;
  grid-template-columns: 100px auto 100px;
  grid-template-rows: repeat(3, 100px);
}
自定义网格线名称

作用:指定每一根网格线的名字,方便以后的引用

实例:

指定网格布局为3*3,因此有4根垂直网格线和4根水平网格线,每个网格线可以指定多个名字,比如[r4 xipiker666]

.container {
  display: grid;
  grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4];
  grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4 xipiker666];
}

grid-columns-gap(columns-gap)与grid-rows-gap(rows-gap)与grid-gap(gap)

作用:grid-columns-gap设置列间距、grid-rows-gap设置行间距,可以分别简写为columns-gaprows-gapgrid-gap设置行列间距grid-gap: <行间距> <列间距>grid-gap可以简写为gap,如果行和列的值相等可以gap: xxpx

实例:

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  gap: 20px;
}

grid-template-areas

作用:网格布局允许指定"区域"(area),一个区域由单个或多个单元格组成

实例:

  • 划分出9个单元格
.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-template-areas: 
    'a b c'
    'd e f'
    'g h i';                 
  gap: 20px;  
}
  • 多个单元格合并成一个区域
.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-template-areas: 
    'a a a'
    'b b b'
    'c c c';                 
  gap: 20px;  
}
  • 某些区域不需要利用,则使用.表示
.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-template-areas: 
    'a . a'
    'b . b'
    'c . c';                 
  gap: 20px;  
}

grid-auto-flow

作用:容器的子元素的排序方式,属性值row先行后列,属性值columns先列后行,属性值row dense子项目指定位置后再先行后列排序,属性值columns dense子项目指定位置后再先列后行排序

实例:

  • row先行后列

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-auto-flow: row;
}
  • columns先列后行

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-auto-flow: column;
}
  • row dense子项目指定位置后先行后列

image.png

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-auto-flow: row dense;
}

.item_first {
  grid-column-start: 1;
  grid-column-end: 3;
}

.item_two {
  grid-column-start: 1;
  grid-column-end: 3;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • columns dense子项目指定位置后先列后行

image.png

.container {
  width: 400px;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  grid-auto-flow: column dense;
}

.item_first {
  grid-column-start: 1;
  grid-column-end: 3;
}

.item_two {
  grid-column-start: 1;
  grid-column-end: 3;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}

justify-items与align-items与place-items

作用:justify-items属性设置单元格内容的水平位置,align-items属性设置单元格内的垂直位置,place-items属性是justify-itemsalign-items的组合属性

实例:

  • justify-items: start | center | end | stretch

image.png

.container {
  width: 300px;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  justify-items: center;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • align-items: start | center | end | stretch

image.png

.container {
  width: 300px;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  align-items: center;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • place-items: <align-items> <justify-items>

image.png

.container {
  width: 300px;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  place-items: center end;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}

justify-content与align-content与place-content

作用:justify-content属性设置内容区域在容器里面的水平位置,align-content属性设置内容区域在容器里面的垂直位置,place-content属性是justify-contentalign-content的组合属性

实例:

  • justify-content: start | end | center | stretch | space-around | space-between | space-evenly

image.png

.container {
  width: 600px;
  background: #f9f9f9;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  justify-content: center;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • align-content: start | end | center | stretch | space-around | space-between | space-evenly

image.png

.container {
  width: 600px;
  height: 400px;
  background: #f9f9f9;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  align-content: center;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • place-content: <align-content> <justify-content>

image.png

.container {
  width: 600px;
  height: 400px;
  background: #f9f9f9;
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  place-content: center center;
}

grid-auto-columns与grid-auto-rows

作用:可以理解为,现有网格的外部设置多余的网格,用来放超出的子项目

实例:

  • grid-auto-columns

image.png

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-columns: 100px 100px;
  grid-template-rows: 50px;
  grid-auto-rows: 80px
  grid-auto-flow: row; /*这里要设置按照行方向填充,可以不设置,因为默认按照行方向*/
}
  • grid-auto-rows

image.png

.container{
  background: #f9f9f9;
  display: grid;
  grid-template-rows: 50px 50px;
  grid-template-columns: 100px;
  grid-auto-columns: 150px; 
  grid-auto-flow: column; /*注意这里要设置按照列方向填充,必须设置,因为默认按照行防线*/
}

grid-template与grid

作用:grid-template属性是grid-template-columnsgrid-template-rowsgrid-template-areas这三个属性的合并简写形式,grid属性是grid-template-rowsgrid-template-columnsgrid-template-areas、 grid-auto-rowsgrid-auto-columnsgrid-auto-flow这六个属性的合并简写形式

四、项目属性

grid-column-start、grid-column-end与grid-row-start、grid-row-end

作用:可以理解为设置项目占多少个网格线,分别定位在哪根网格线

实例:

  • grid-column-start: <number>左边框所在的垂直网格线、grid-column-end: <number>右边框所在的垂直网格线

image.png

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(3, 100px);
}

.item_first {
  grid-column-start: 1;
  grid-column-end: 3;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
<div class="container">
    <div className="item_first">1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
</div>
  • grid-row-start: <number>上边框所在的垂直网格线、grid-row-end: <number>下边框所在的垂直网格线

image.png

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(3, 100px);
}

.item_first {
  grid-row-start: 1;
  grid-row-end: 3;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
<div class="container">
    <div className="item_first">1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
</div>

grid-column与grid-row

作用:grid-column属性是grid-column-startgrid-column-end的简写形式、grid-row属性是grid-row-startgrid-row-end的简写形式

实例:

  • grid-column: <grid-column-start> / <grid-column-end>
  • grid-row: <grid-row-start> / <grid-row-end>

grid-area

作用:指定项目放在哪个区域,grid-area属性还可用作grid-row-startgrid-column-startgrid-row-endgrid-column-end的合并简写形式,直接指定项目的位置

实例:

  • 指定放在哪个区域

image.png

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(3, 100px);
  grid-template-areas:
    'a b c'
    'd e f'
    'g h i';
}

.item_first {
  grid-area: e;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
  • grid-area: <row-start> / <column-start> / <row-end> / <column-end>

image.png

@pos: ~'1 / 1 / 3 / 3';

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(3, 100px);
}

.item_first {
  grid-area: @pos;
}

.container > div {
  width: 100%;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}

justify-self与align-self与place-self

作用:justify-self属性设置单元格内容的水平位置、align-self属性设置单元格内容的垂直位置,place-self属性是justify-selfalign-self的属性组合

实例:

image.png

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(3, 100px);
}

.item_first {
  justify-self: center;
  align-self: center;
}

.container > div {
  width: 60px;
  height: 60px;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}

五、常见布局案例

三列等宽布局

效果

image.png

代码

.container {
  background: #f9f9f9;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.container > div {
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  font-weight: bold;
}
<div className="container">
    <div>1</div>
    <div>2</div>
    <div>3</div>
</div>

圣杯布局

效果

image.png

代码

.container {
  display: grid;
  grid-template-areas:
    'header header'
    'sidebar main'
    'footer footer';
  grid-template-columns: 200px 1fr;
  grid-template-rows: 60px 1fr 40px;
  gap: 10px;
}
.header {
  grid-area: header;
  background: lightblue;
}
.sidebar {
  grid-area: sidebar;
  background: lightgoldenrodyellow;
}
.main {
  grid-area: main;
  background: lightgreen;
}
.footer {
  grid-area: footer;
  background: lightpink;
}
<div class="container">
    <div className="header">header</div>
    <div className="sidebar">sidebar</div>
    <div className="main">main</div>
    <div className="footer">footer</div>
</div>

媒体查询响应式布局(Gird+Flex最佳拍档)

效果

屏幕宽度小于等于660px

image.png

屏幕宽度小于等于1024

image.png

屏幕宽度大于1024

image.png代码

<div class="dashboard">
    <div class="stats">Stats</div>
    <div class="overview">Overview</div>
    <div class="lifecycle">Lifecycle</div>
    <div class="calendar">Calendar</div>
    <div class="monitor">Monitor</div>
    <div class="ladder">Ladder</div>
    <div class="stage">Stage</div>
</div>
.dashboard {
  display: grid;
  gap: 20px;
  padding: 20px;
  /* 桌面端布局 */
  grid-template-columns: 2fr 2fr 1fr;
  grid-template-areas:
    'stats stats stats'
    'overview lifecycle calendar'
    'monitor ladder calendar'
    'stage stage stage';
}

.dashboard > div {
  background: #eee;
  padding: 10px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  font-weight: bold;
}

.stats {
  grid-area: stats;
}
.overview {
  grid-area: overview;
}
.lifecycle {
  grid-area: lifecycle;
}
.calendar {
  grid-area: calendar;
}
.monitor {
  grid-area: monitor;
}
.ladder {
  grid-area: ladder;
}
.stage {
  grid-area: stage;
}

@media (max-width: 1024px) {
  .dashboard {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      'stats stats'
      'overview lifecycle'
      'calendar calendar'
      'monitor ladder'
      'stage stage';
  }
}

@media (max-width: 600px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas:
      'stats'
      'overview'
      'lifecycle'
      'calendar'
      'monitor'
      'ladder'
      'stage';
  }
}

Grid瀑布流布局

效果

image.png

代码

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-auto-rows: 10px;
  grid-auto-flow: dense;
  gap: 10px;
}
.item {
  background: lightblue;
}
.item1 {
  grid-row-end: span 15;
}
.item2 {
  grid-row-end: span 25;
}
.item3 {
  grid-row-end: span 20;
}
<div class="container">
    <div class="item item1">1</div>
    <div class="item item2">2</div>
    <div class="item item3">3</div>
    <div class="item item1">4</div>
</div>

六、总结

上述内容主要参考,大家可以参考原文,如果能帮到您,欢迎点赞、收藏、+关注

CSS Grid 网格布局教程 - 阮一峰的网络日志

element-plus主题配置及动态切换主题

作者 天外来物
2025年11月18日 14:02

创建vue项目的不同方式

pnpm create vite 使用于创建一些组件库,第三方库的时候

pnpm create vue 适用于vue项目,内部有一些基础的样式,vue的主题,

element-plus主题配置

按需引入element

1. scss变量自定义主题

分支:feature-element-theme-anxu-scss

src/styles/element/index.scss

/* just override what you need */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': green,
    ),
  )
);

vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import ElementPlus from 'unplugin-element-plus/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
      ],
    }),
    ElementPlus({
      useSource: true,
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/element/index.scss" as *;`,
      },
    },
  },
})

按需引入组件时,使用组件,会自动引入对应组件的样式,不用再main.ts中引入elememnt的全部样式。

主题配置只需要修改需要改变的颜色变量即可

可以看到,对应组件的颜色变量已经变化

image.png

2. css变量自定义主题

分支: feature-element-theme-anxu-css

src/styles/element/index.scss

:root {
  --el-color-primary: green;
}

vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import ElementPlus from 'unplugin-element-plus/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
      ],
    }),
    ElementPlus({
      useSource: true,
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/element/index.scss" as *;`,
      },
    },
  },
})

可以看到,css变量被注入到组件对应的scss文件顶部,从而让主题生效

image.png

全量引入element

scss全量引入

分支 feature-element-theme-all-scss

/* just override what you need */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': green,
    ),
  )
);

// If you just import on demand, you can ignore the following content.
// 如果你是全量导入,需要加下边这句,如果是按需引入,请注释掉下边这句
@use 'element-plus/theme-chalk/src/index.scss' as *;

main.ts

import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import './styles/element/index.scss'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.use(ElementPlus)
app.mount('#app')

这时候可以看到样式被全部引入了

image.png

css全量引入

分支:feature-element-theme-all-css src/styles/element/index.css

:root {
  --el-color-primary: green;
}

main.ts

import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './styles/element/index.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.use(ElementPlus)
app.mount('#app')

可以看到,element的css变量已经被覆盖了

image.png

动态主题

运行时动态切换主题,不能依赖scss变量

原因

SCSS 变量是编译时变量,在构建阶段就被替换掉了,在浏览器运行时无法再改变,所以无法用于“动态切换主题”。

动态主题应该使用css变量来实现

官方提供了一个切换主题的项目github.com/element-plu…

里面引入了一套暗黑模式的样式,然后通过 useToggle进行切换

分支 feature-element-theme-anxu-scss-dynamic 还是采用按需引入elememnt的方式

src/styles/index.scss 这里全量引入暗黑模式的样式

// import dark theme
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;

// :root {
//   --ep-color-primary: red;
// }

body {
  font-family:
    Inter, system-ui, Avenir, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
    'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  margin: 0;
}

a {
  color: var(--ep-color-primary);
}

main.ts在main.ts中引入样式

// import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import '@/styles/index.scss'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

src/styles/element/index.scss 这里是自定义默认主题变量

$--colors: (
  'primary': (
    'base': rgb(0, 128, 19),
  ),
  'success': (
    'base': #21ba45,
  ),
  'warning': (
    'base': #f2711c,
  ),
  'danger': (
    'base': #db2828,
  ),
  'error': (
    'base': #db2828,
  ),
  'info': (
    'base': #42b8dd,
  ),
);

// You should use them in scss, because we calculate it by sass.
// comment next lines to use default color
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  // do not use same name, it will override.
  $colors: $--colors // $button-padding-horizontal: ('default': 50px)
);

// if you want to import all
// @use "element-plus/theme-chalk/src/index.scss" as *;

// You can comment it to hide debug info.
// @debug $--colors;

// custom dark variables
@use './dark.scss';

src/styles/element/dark.scss 这是对暗黑主题颜色重新定义

// only scss variables

$--colors: (
  'primary': (
    'base': #589ef8,
  ),
);

@forward 'element-plus/theme-chalk/src/dark/var.scss' with (
  $colors: $--colors
);

vite.config.ts 这里引入自定义主题变量,自定义默认主题和暗黑主题颜色

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import ElementPlus from 'unplugin-element-plus/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
      ],
    }),
    ElementPlus({
      useSource: true,
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/element/index.scss" as *;`,
      },
    },
  },
})

动态切换主题

  • 通过useDark获取当前是否是暗黑模式
  • 通过useToggle切换主题
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
const toggleDark = useToggle(isDark)
const changeTheme = () => {
  toggleDark()
}
</script>

<template>
  <header>
    <el-button type="primary" @click="changeTheme"> 切换主题 </el-button>
    <el-input></el-input>
  </header>

  <RouterView />
</template>

<style scoped>
header {
  line-height: 1.5;
  max-height: 100vh;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

nav {
  width: 100%;
  font-size: 12px;
  text-align: center;
  margin-top: 2rem;
}

nav a.router-link-exact-active {
  color: var(--color-text);
}

nav a.router-link-exact-active:hover {
  background-color: transparent;
}

nav a {
  display: inline-block;
  padding: 0 1rem;
  border-left: 1px solid var(--color-border);
}

nav a:first-of-type {
  border: 0;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }

  nav {
    text-align: left;
    margin-left: -1rem;
    font-size: 1rem;

    padding: 1rem 0;
    margin-top: 1rem;
  }
}
</style>

代码仓库地址 github.com/Stacey1018/…

❌
❌