普通视图

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

面试问“如何设计可扩展 Button”?像素风格button改造只要 10分钟!

作者 孟祥_成都
2025年11月20日 14:59

前言

面试官:(打着哈欠,眼皮打架)你说你写的组件库拓展性很好,怎么证明呢?

:(自信满满)您直接去我的组件库官网看,这是传统组件库样式,大家都能做:

image.png

地址:上图页面地址

:(切换页面)然后这是我用自己写的 @t-headless-ui/react 中的同一个 Button 组件,只是稍微改了下样式。比如你想要像素风格,请看

image.png

地址:上图页面地址

(如果您觉得不错,请求 github 一个赞,我一定会拓展所有主流组件到其中的!)

面试官:(原本昏昏欲睡,突然瞪大眼睛,从凳子上一跃而起)"卧槽!兄弟!你这...这怎么做到的?!"

:(淡定一笑)无它,headless 组件库而已!

面试官:(扶了扶惊掉的下巴)"headless 组件库是什么意思?"

:"简单说就是——我们只提供JavaScript逻辑和功能,样式你爱咋写咋写!甚至连DOM结构都随便你折腾!"

面试官:(沉默三秒,突然起身握住我的手)"好!我服!明天就来上班!工资你随便开!"

当然,上面只是一个小玩笑,哈哈,今天我们就来介绍一下,如何写一个像素风格的 button 以及为啥我的 headless button 稍微改一下样式,就可以造一个生产级别能用的 button 呢?

一个生产环境要用的 button 需要具备什么样的属性呢?

button 的状态

一个 button 至少需要:

  • 定义 disabled 状态,在 disabled 状态, click 事件是无法生效的
  • 定义 loading 状态,在 loading 状态, click 事件同样是是无法生效的

headless 也就是无样式组件的层面上,做到拦截这两个属性的 click 事件就足够了.

是不是很简单,只是因为 button 的复杂度更多在于定制样式,所以 headless 组件库的 button 很容易实现。

核心代码如下:

  const handleClick: MouseEventHandler = (event): void => {
    if (loading || disabled) {
      // 阻止按钮默认行为,防止在 loading 或 disabled 状态下意外提交表单或触发其他默认事件
      event?.preventDefault?.();
      return;
    }
    onClick?.(event);
  };

好了,接下来我们说样式,样式的难度主要在于状态很多,包括:

  • 各种主题色
    • primary:主色
    • success: 成功色
    • error:失败颜色
    • 。。。 然后每个状态都分为,default 状态,disabled 状态,loading 状态
  • 各种风格
    • 有背景色的风格
    • 只有边框的风格
    • 。。。

然后每个状态都分为,default 状态,disabled 状态,loading 状态

还有需要考虑 focus 上的颜色状态,hover 的颜色状态。

这才是一个 button 真正复杂的点!

如果你想学造组件库,欢迎加入,我们超级可拓展的 headless 组件库交流群(还有包装简历服务哦!)主页

我们现在为了说明这个像素 button 实现的思路,我们化繁为简,假设只有 1 种颜色,1 种风格,如下图:

image.png

说明实现思路是如何,大家可以触类旁通!

如何制作出像素效果

从上面图可以看出,最明显的像素想过就是我们死角都是 transparent(透明色),这个明显是 border 实现不了的。那如何做呢?

秘密就是 box-shadow !

我们先举一个小例子,你看看 box-shadow 如何使用,假设我们需要一个下边框,box-shadow 设置为:

box-shadow: 0 5px black;

其中 0 代表横向的偏移量,我们是 0 ,也就是横轴上什么也不做,5px 代表在竖直方向上向下制造一个 5pxshadow, 然后颜色设置为 black 也就是黑色,请看这个按钮下方多了一条黑色

image.png

接着,我们依葫芦画瓢,加上 上面的 shadow

box-shadow: 0 5px black, 0 -5px black;

效果如下

image.png

接着右边我们也加一下:

box-shadow: 0 5px black, 0 -5px black, 5px 0 black;

image.png

是不是有感觉了,shadowborder 最大的不同就是边角不会填充颜色,然后接着,我们加上又边框:

box-shadow: 0 5px black, 0 -5px black, 5px 0 black, -5px 0 black;

最后我们再润色一下,加一个上阴影,增加立体感,如下图

image.png

box-shadow: 0 5px black, 0 -5px black, 5px 0 black, -5px 0 black, inset 0px 4px rgba(255,255,255,0.21);

其中 inset 的意思是增加内阴影,之前我们都是外阴影,rgba(255,255,255,0.21) 是指白色,然后 0.21 的透明度。

初始化 button

在我们设置 box-shadow 之前,这个 button 背景色,字体这些还没初始化,我们先来补上这个知识

{
    position: relative;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    user-select: none;
    padding-top: .25rem;
    padding-bottom: .25rem;   
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    background-color: rgb(88,199,192);
    border-width: 0;
    border-radius: 0;
}

好了初始化完毕,我们开始处理各种状态:

hover 状态

hover 状态我们用了一个 filter 函数,增加亮度。

filter:brightness(1.05)

但是同时注意,在 disabledloading 状态 hover 无效。在 react 一般都要使用 classnames 库,tailwindcss 还要将 classnamestailwindcss-merge 库结合, vue 好像天然对于 css 的条件判断就支持的不错。

类似代码如下(不同的框架和使用的 css 框架不同,代码肯定有不同,这个很简单,大家看得懂意思即可):

{
   'hover:brightness-105': !disabled && !loading
}

'hover:brightness-105' 是 tailwindcss 中的类名, 对于普通的 css 你只要换成你希望的类名,然后引入对应的 css 文件即可(在 css 文件里实现这个 hover 的 css).

active 状态

active 状态,我们只是把内阴影变黑了,给人一种凹凸下去的感觉。 active 下的 box-shadow 变为

box-shadow: 0px 5px black,0px -5px black,5px 0px black,-5px 0px black,inset 0px 5px #00000038

可以看到只是最后 inset 中的颜色变深了,其它都没有变。

处理 disabled 状态

这个状态其实需要判断,比如 disabled 的时候 cursor ,也就是鼠标显示一个禁止点击的状态,这样有利于告诉用户此时点击无效。

类似的增加对 disabled 状态的处理:

{
    'opacity-50 cursor-not-allowed': disabled, // disabled 时透明度降低,cursor 变为 not-allowed
}

字体更想像素的样子

这个最好是搜索有像素风格的字体样式,我是为了方便,使用系统自带中,类似有像素风格的字体,大家可以参考,兼容macwindows

{
    font-family: 'ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Lucida_Console','Courier_New',monospace'
}

dark 模式

我使用的是 tailwindcss 天然对 dark 模式可以自定义颜色,国内很多人不使用 taiwindcss 这里就不赘述了

小总结

这里就结束了,这一种状态看起来代码不多,其实状态和颜色多了之后,css 代码会非常多。最后,大家可以看到 headless 组件的好处就是跟样式没有任何耦合,随便用各种 css 框架自己处理,它的好处我会在后面的文章继续介绍,例如 radio 组件,本质上就是单选,我们看到很多组件库都是一个圆形按钮的样式,其实谁规定了必须是这个样式?我可以是任何只要满足单选的样式即可。

所以后续会有很多有趣的主题加入进来,包括button,也会持续更新更重好玩的样式和动画!

最后你到看到这里了,欢迎各位大侠来组件库交流群交流,也希望各位大侠给个github的赞,嘿嘿

20个CSS实用技巧,10分钟从小白变大神!

作者 刘大华
2025年11月21日 10:48

大家好,我是大华!今天给大家分享一些比较实用的CSS技巧,不管你是前端新手还是老司机,这些技巧都能让你的开发效率翻倍!废话不多说,直接上代码了。

1. 使用变量维护主题色

/* 定义CSS变量 */
:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --text-color: #333;
}

/* 使用变量 */
.button {
  background-color: var(--primary-color);
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
}

.card {
  border: 1px solid var(--secondary-color);
  color: var(--text-color);
}

适用场景:需要维护统一设计规范的项目,方便快速切换主题

2. Flex布局居中(终极解决方案)

.center-container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center;     /* 垂直居中 */
  height: 100vh;          /* 视口高度 */
}

/* 如果只需要单方向居中 */
.vertical-center {
  display: flex;
  align-items: center;    /* 仅垂直居中 */
}

.horizontal-center {
  display: flex;
  justify-content: center; /* 仅水平居中 */
}

适用场景:任何需要居中的布局,特别是响应式设计

3. Grid网格布局

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3等分列 */
  grid-gap: 20px;                       /* 网格间距 */
  padding: 20px;
}

.grid-item {
  background: #f0f0f0;
  padding: 20px;
  border-radius: 8px;
}

/* 响应式网格 */
@media (max-width: 768px) {
  .grid-container {
    grid-template-columns: repeat(2, 1fr); /* 小屏幕2列 */
  }
}

@media (max-width: 480px) {
  .grid-container {
    grid-template-columns: 1fr; /* 超小屏幕1列 */
  }
}

适用场景:复杂布局、卡片布局、图片画廊

4. 自定义滚动条

.custom-scrollbar {
  width: 300px;
  height: 200px;
  overflow-y: scroll;
  padding: 10px;
}

/* Webkit浏览器滚动条样式 */
.custom-scrollbar::-webkit-scrollbar {
  width: 8px; /* 滚动条宽度 */
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: #f1f1f1; /* 轨道背景 */
  border-radius: 4px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background: #c1c1c1; /* 滑块颜色 */
  border-radius: 4px;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8; /* 悬停颜色 */
}

适用场景:需要统一浏览器滚动条样式的项目

5. 文字渐变效果

.gradient-text {
  background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
  -webkit-background-clip: text; /* 关键:背景裁剪到文字 */
  -webkit-text-fill-color: transparent; /* 关键:文字透明 */
  background-clip: text;
  font-size: 3rem;
  font-weight: bold;
}

适用场景:标题设计、品牌文字、特色展示

6. 毛玻璃效果

.frosted-glass {
  background: rgba(255, 255, 255, 0.25); /* 半透明背景 */
  backdrop-filter: blur(10px);           /* 背景模糊 */
  -webkit-backdrop-filter: blur(10px);   /* Safari支持 */
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 10px;
  padding: 20px;
  color: white;
}

适用场景:现代设计风格、卡片悬浮效果、导航栏

7. 悬浮阴影动画

.card {
  background: white;
  border-radius: 10px;
  padding: 20px;
  transition: all 0.3s ease; /* 所有属性过渡0.3秒 */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.card:hover {
  transform: translateY(-5px); /* 悬浮上移5px */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); /* 阴影加深 */
}

适用场景:卡片设计、按钮交互、产品展示

8. 自定义复选框

/* 隐藏原生复选框 */
.custom-checkbox input[type="checkbox"] {
  display: none;
}

/* 自定义复选框样式 */
.checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  font-size: 16px;
}

.checkmark {
  width: 20px;
  height: 20px;
  border: 2px solid #ddd;
  border-radius: 4px;
  margin-right: 10px;
  position: relative;
  transition: all 0.3s ease;
}

/* 选中状态 */
.custom-checkbox input[type="checkbox"]:checked + .checkbox-label .checkmark {
  background: #3498db;
  border-color: #3498db;
}

/* 选中后的对勾 */
.custom-checkbox input[type="checkbox"]:checked + .checkbox-label .checkmark::after {
  content: "";
  position: absolute;
  left: 6px;
  top: 2px;
  width: 6px;
  height: 10px;
  border: solid white;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

适用场景:表单美化、统一设计语言

9. 图片悬浮放大

.image-container {
  width: 300px;
  height: 200px;
  overflow: hidden; /* 隐藏溢出部分 */
  border-radius: 8px;
}

.zoom-image {
  width: 100%;
  height: 100%;
  object-fit: cover; /* 保持图片比例 */
  transition: transform 0.5s ease; /* 缩放过渡 */
}

.zoom-image:hover {
  transform: scale(1.1); /* 悬浮放大1.1倍 */
}

适用场景:产品图集、相册、作品展示

10. 文字阴影效果

.text-shadow {
  font-size: 3rem;
  font-weight: bold;
  color: #2c3e50;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); /* 水平 垂直 模糊 颜色 */
}

/* 多层阴影效果 */
.multi-shadow {
  font-size: 3rem;
  font-weight: bold;
  color: #e74c3c;
  text-shadow: 
    1px 1px 0 #c0392b,
    2px 2px 0 #922b21,
    3px 3px 5px rgba(0, 0, 0, 0.6);
}

适用场景:标题设计、文字特效、海报设计

11. 渐变边框

.gradient-border {
  padding: 20px;
  border: 4px solid;
  border-image: linear-gradient(45deg, #ff6b6b, #4ecdc4) 1;
  border-radius: 8px;
  background: white;
}

/* 另一种实现方式 */
.gradient-border-alt {
  padding: 20px;
  background: 
    linear-gradient(white, white) padding-box,
    linear-gradient(45deg, #ff6b6b, #4ecdc4) border-box;
  border: 4px solid transparent;
  border-radius: 8px;
}

适用场景:特色边框、高亮元素、品牌标识

12. 粘性定位

.sticky-header {
  position: sticky;
  top: 0; /* 距离顶部0时固定 */
  background: white;
  padding: 15px 0;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000; /* 确保在最上层 */
}

.sticky-sidebar {
  position: sticky;
  top: 100px; /* 距离顶部100px时固定 */
  align-self: start; /* Grid布局中对齐 */
}

适用场景:导航栏、侧边栏、表头固定

13. 文字溢出省略

/* 单行文字溢出显示省略号 */
.single-line-ellipsis {
  white-space: nowrap;      /* 不换行 */
  overflow: hidden;         /* 隐藏溢出 */
  text-overflow: ellipsis;  /* 显示省略号 */
  width: 200px;
}

/* 多行文字溢出显示省略号 */
.multi-line-ellipsis {
  display: -webkit-box;
  -webkit-line-clamp: 3;    /* 显示3行 */
  -webkit-box-orient: vertical;
  overflow: hidden;
  width: 300px;
}

适用场景:标题截断、内容摘要、表格单元格

14. 自定义选择文本样式

::selection {
  background: #3498db;    /* 选中背景色 */
  color: white;           /* 选中文字颜色 */
  text-shadow: none;      /* 去除文字阴影 */
}

/* 针对不同浏览器前缀 */
::-moz-selection {
  background: #3498db;
  color: white;
  text-shadow: none;
}

适用场景:提升用户体验、品牌一致性

15. 滤镜效果

.image-filters {
  transition: filter 0.3s ease;
}

/* 灰度效果 */
.grayscale {
  filter: grayscale(100%);
}

.grayscale:hover {
  filter: grayscale(0%);
}

/* 多个滤镜组合 */
.multiple-filters {
  filter: brightness(1.2) contrast(0.8) saturate(1.5);
}

/* 模糊效果 */
.blur-effect {
  filter: blur(2px);
}

适用场景:图片特效、主题切换、视觉设计

16. 动画关键帧

@keyframes bounce {
  0%, 100% {
    transform: translateY(0); /* 起始和结束位置 */
  }
  50% {
    transform: translateY(-20px); /* 跳起位置 */
  }
}

.bouncing-element {
  animation: bounce 2s ease-in-out infinite; /* 动画名称 时长 缓动 重复 */
}

/* 加载动画 */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

适用场景:加载动画、交互动效、注意力引导

17. 响应式图片

.responsive-image {
  max-width: 100%;    /* 最大宽度100% */
  height: auto;       /* 高度自动 */
  display: block;     /* 块级元素 */
}

/* 保持比例的容器 */
.aspect-ratio-box {
  width: 100%;
  height: 0;
  padding-bottom: 56.25%; /* 16:9比例 (9/16=0.5625) */
  position: relative;
  overflow: hidden;
}

.aspect-ratio-box img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover; /* 覆盖整个容器 */
}

适用场景:响应式网站、图片画廊、产品展示

18. 三角形绘制

.triangle-up {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid #3498db;
}

.triangle-down {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-top: 100px solid #e74c3c;
}

.triangle-left {
  width: 0;
  height: 0;
  border-top: 50px solid transparent;
  border-bottom: 50px solid transparent;
  border-right: 100px solid #2ecc71;
}

适用场景:工具提示、下拉箭头、装饰元素

19. 文字描边效果

.text-stroke {
  font-size: 3rem;
  font-weight: bold;
  color: white;
  -webkit-text-stroke: 2px #3498db; /* 描边宽度和颜色 */
  text-stroke: 2px #3498db;
}

/* 兼容写法 */
.text-stroke-alt {
  font-size: 3rem;
  font-weight: bold;
  color: transparent;
  -webkit-text-stroke: 2px #3498db;
  text-shadow: none;
}

适用场景:标题设计、海报文字、特色展示

20. 混合模式

.blend-mode {
  background-image: url('background.jpg');
  background-size: cover;
}

.blend-mode::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #3498db;
  mix-blend-mode: overlay; /* 叠加混合模式 */
}

/* 图片混合模式 */
.image-blend {
  background: 
    linear-gradient(45deg, #ff6b6b, #4ecdc4),
    url('image.jpg');
  background-blend-mode: overlay;
  background-size: cover;
}

适用场景:创意设计、图片处理、视觉效果


总结

这20个CSS技巧包含了现代最常见的开发需求,从布局到动画,从响应式到视觉效果。掌握这些技巧,你就能应对90%的日常开发需求。

其中有几点比较关键:

  1. 变量管理让维护更轻松
  2. Flex/Grid是布局的首选
  3. 过渡和动画提升用户体验

希望这些技巧能帮助你在开发中事半功倍!

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《SpringBoot+Vue3 整合 SSE 实现实时消息推送》

《这20条SQL优化方案,让你的数据库查询速度提升10倍》

《SpringBoot 动态菜单权限系统设计的企业级解决方案》

《Vue3 + ElementPlus 动态菜单实现:一套代码完美适配多角色权限系统》

昨天以前首页

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/…

❌
❌