聊到页面配色,很多前端同行有一个共同习惯:颜色全靠十六进制。设计稿写 #1E90FF 就 copy 过来用,想改浅一点就在编辑器里试 #4FA8FF、#73BFFF,来回调几次眼睛都花了。等真正开始研究 CSS 颜色函数和渐变 之后,我才发现这套方案能节省大量微调时间:颜色可以按"色相+饱和度+亮度"去理解,渐变可以精准控制方向和位置,甚至不用依赖设计工具就能算出需要的结果。
这篇内容会把 CSS 颜色函数和渐变的前世今生、底层逻辑、常用写法、真实项目中的踩坑点一次说清楚,并且针对热搜里反复出现的字体渐变、涟漪光圈扩散、金光闪闪效果、波浪背景等场景给出可直接复制的代码。不管你是刚接触 CSS 的新手,还是用十六进制写了好几年页面的老手,都能从中找到自己需要的那块拼图。
这里我啰嗦一句:颜色和渐变是 CSS 里视觉表现最强的部分,也是面试常考的部分,把这块吃透,写出来的页面会明显比"只会贴色值"的同行高一个档次。
1. 调色思路升级:从十六进制到函数式颜色
1.1 十六进制为什么不够用:颜色是写出来的,不是看出来的
#FF6347 这个颜色,你能第一时间在脑海里想象出它长什么样吗?大部分人不行。十六进制本质上是 RGB 三个通道的十六进制拼接,它的设计初衷是给机器读的,不是给人看的。你可以背下 #3498db 是蓝色,#e74c3c 是红色,但一旦要求你"把这个颜色调暗 20%、饱和度降低 10%",靠十六进制手算是非常痛苦的。
函数式颜色(functional color notation)把颜色拆成有语义的参数,比如:
css复制color: rgb(255, 99, 71); /* RGB三通道 */
color: hsl(9, 100%, 64%); /* 色相/饱和度/亮度 */
同样的红色,用 hsl 表示就是 hsl(9, 100%, 64%),其中 9 是色相(偏橙红),100% 是饱和度,64% 是亮度。改动饱和度或亮度只是动一个数字的事,不需要在三个通道之间跳来跳去。
从工程角度看,函数式颜色还带来两个隐藏好处。第一,颜色可以参与变量和计算,后面讲 color-mix() 时会看到威力。第二,透明度参数可以直接挂在函数里,rgba()、hsla() 不用额外的 opacity 属性,避免影响子元素。
注意:现在
rgb()和hsl()的代码写法已经允许直接写空格分割,比如rgb(255 99 71 / 0.5),不再需要逗号和 a 字母版本(rgba()/hsla()是历史遗留写法,功能与不带 a 的版本完全一致)。
1.2 hsl() 为什么被低估:不打开设计软件也能改色
我刚入行时,团队里有位前辈调颜色从不打开设计软件,直接在浏览器开发者工具里改 hsl 值,速度非常快。后来我也养成了这个习惯。hsl 的核心优势在于它和人眼理解颜色的方式一致:先确认是什么色相(0-360 度),再决定色彩鲜艳程度(饱和度),最后定明暗(亮度)。
举个实际例子,一个按钮默认色是 hsl(210, 80%, 55%),hover 状态想深一点,直接把亮度降到 45% 即可:
css复制.btn {
background: hsl(210, 80%, 55%);
}
.btn:hover {
background: hsl(210, 80%, 45%);
}
如果想让按钮更"跳",把饱和度调到 90% 就行。整个过程不需要计算任何中间色,也不需要打开调色板。而用十六进制,你会发现从 #3b9eff 变到 #2a8de8 这种肉眼才能判断的修改,效率完全不是一个级别。
hsl 还有一个容易踩的坑:饱和度越高,颜色越接近纯色;亮度是 0% 时是黑色,100% 时是白色,中间值才是"正常"状态。很多人第一次把 hsl(0, 100%, 0%) 当成红色,结果画出来是纯黑,原因就是亮度写成 0% 了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 新一代颜色函数逐个过:hwb、lab、lch、color-mix 怎么选
2.1 hwb():直接控制白和黑,新手最友好的模型
hwb 是 Hue-Whiteness-Blackness 的缩写,意思是色相 + 白度 + 黑度。这恐怕是所有颜色模型里最直观的一个:先选一个色相,然后往里面掺白色,再往里面掺黑色,掺多少用百分比控制。
css复制color: hwb(210 20% 10%); /* 色相210度,掺20%白,10%黑 */
这种写法对"做同一色系的深浅变化"特别友好。比如一个蓝色主题,需要从深到浅五个层次,只要固定色相 210,然后白度从 10% 递增到 70%,黑度从 40% 递减到 0%,就能得到一套肉眼观感很统一的色阶。相比 hsl 需要同时调饱和度和亮度两条曲线,hwb 只需要同时调两个简单参数,心智负担小得多。
hwb 的兼容性目前已经比较好了,现代浏览器基本都支持。我在实际项目里用它做设计系统的色板生成脚本,输出结果比之前用 hsl 拼色板更可控。
2.2 lab() 和 lch():人眼感知均匀,干设计系统的利器
lab 和 lch 是 CIE 实验室色彩空间在 CSS 里的体现。这里不展开讲色彩科学的细节,只需要知道一个关键特性:Lab 空间是"感知均匀"的,两个颜色在数值上差多少,人眼感知差异就大约差多少。而 RGB 和 HSL 不是这样,比如在纯绿色附近,数值变化一点点,人眼感觉差异很大;在蓝色附近,数值变化很多,人眼却不太敏感。
css复制color: lab(50% 20 30); /* L:亮度,a:绿到红,b:蓝到黄 */
color: lch(50% 40 30); /* L:亮度,C:彩度,H:色相 */
lab/lch 对普通页面开发来说,最大的用途是保证颜色一致性和可访问性。做设计系统时,如果直接在 RGB 空间里做色阶,经常出现"看起来亮度一致,实际感知深浅不同"的问题;换到 lch 空间,把亮度 L 锁在一个固定值,生成出来的色阶视觉上非常均衡。
不过要坦白说,如果你的项目只需要几个常规颜色,lab/lch 的收益不明显,还会增加代码的可读性成本。它更适合大型设计系统、主题定制、自动化配色这类场景。我也只在做组件库色板时用它,日常页面还是 hsl 为主。
2.3 color() 与 color-mix():不用 Sass/Less 也能混合颜色
color() 函数允许在代码里显式指定色彩空间,例如:
css复制color: color(display-p3 1 0 0); /* P3广色域下的纯红 */
display-p3 能显示比 sRGB 更广的颜色范围,在支持 P3 的屏幕上颜色会更鲜艳。不过实际项目里用得不算多,原因很简单:要考虑不支持 P3 的浏览器降级策略,通常会先写一个 sRGB 值,再在支持时覆盖。
比 color() 更常用的是 color-mix()。它直接解决了一个老难题:混合两个颜色。
css复制color-mix(in srgb, #ff0000, #0000ff);
/* 50%红 + 50%蓝 = 紫色 */
color-mix(in srgb, #ff0000 30%, #0000ff);
/* 30%红 + 70%蓝,未指定百分比的另一个颜色自动补足 */
以前做这件事要靠 Sass 的 mix() 函数或者预计算,现在原生 CSS 就能实现。配合 CSS 变量,可以做非常灵活的运行时配色。比如一个产品有多套主题,主色变量是 --brand,辅助按钮颜色直接用 color-mix(in srgb, var(--brand), white 20%) 生成,换主色时辅助按钮颜色自动跟着变,不需要写死一堆色值。
我强烈建议把 color-mix() 加入日常工具箱,哪怕只是为了少写几个"主色的浅色版本"色素值,都值了。
3. 渐变三件套实战拆解:linear、radial、conic
3.1 linear-gradient():角度和色标是核心
线性渐变是最简单也最常用的渐变形式。语法核心就两部分:方向 + 色标列表。
css复制background: linear-gradient(45deg, #ff9a9e, #fad0c4);
方向可以写角度(0deg 是从下到上,90deg 是从左到右),也可以写 to top、to right bottom 这种关键词组合。很多人不知道的是,默认方向是 to bottom,也就是从上往下。如果不写方向,就是从顶部开始往下渐变。
色标(color stop)支持多个颜色,还可以指定每个颜色的起始位置:
css复制background: linear-gradient(
to right,
#ff9a9e 0%,
#fad0c4 30%,
#fbc2eb 100%
);
这里 #ff9a9e 在起点,#fad0c4 在 30% 位置,#fbc2eb 在终点,颜色在每两个色标之间平滑过渡。如果希望颜色之间有一条明显的分界线,可以把两个色标放在同一个位置,比如:
css复制background: linear-gradient(
to right,
#3498db 0%,
#3498db 50%,
#e74c3c 50%,
#e74c3c 100%
);
这就是一个左蓝右红的硬分割背景,切割线在 50% 处。这个技巧在做进度条、分割按钮、双色标签时非常实用,不用额外加一个元素。
实际调渐变时,我有一个习惯:先写一个 45deg 的临时渐变观察过渡效果,再慢慢调角度和色标。45deg 能同时体现水平和垂直方向的过渡,是最容易看出问题的角度。
3.2 radial-gradient():圆润的过渡,适合光晕和遮罩
径向渐变是从中心点向四周扩散的渐变,适合做光晕、发光按钮、遮罩层。语法和 linear 类似,但方向变成了"中心点 + 形状 + 半径":
css复制background: radial-gradient(circle at center, #fff, #3498db);
circle at center 表示以中心点为圆心的圆形渐变。除了 circle,还有 ellipse(椭圆,默认值);at 后面可以写 left、right、top、bottom、坐标值等。半径可以写成 closest-side、farthest-side、closest-corner、farthest-corner,分别表示渐变范围到最近边、最远边、最近角、最远角。
我实际用得最多的是这个组合:径向渐变做按钮高光。
css复制.btn-highlight {
background:
radial-gradient(circle at 50% 0%, rgba(255,255,255,0.3), transparent 50%),
#3498db;
}
第一层半透明白色光晕从上端打下来,第二层是实底蓝色,组合出的按钮有轻微的光泽感,不需要切图,任何尺寸都适用。
3.3 conic-gradient():饼图、仪表盘、进度环都靠它
圆锥渐变(conic-gradient)是按角度分布的渐变,绕中心点逆时针一圈。它有个超经典的应用场景:饼图。
css复制.pie {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(
#3498db 0% 25%,
#e74c3c 25% 60%,
#f1c40f 60% 100%
);
}
这样就画出了一个三色饼图,第一段蓝色占 0%-25%,第二段红色占 25%-60%,第三段黄色占 60%-100%。这种写法很直观,做静态数据展示非常省事。
如果想做进度环,还要配合一个遮罩圆:
css复制.ring {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(#3498db 0% 75%, #eee 75% 100%);
position: relative;
}
.ring::after {
content: "";
position: absolute;
inset: 10px;
border-radius: 50%;
background: #fff;
}
核心思路:外层用 conic-gradient 画出有颜色的环形扇区,内层用白色圆形遮住中心,露出一个环。百分比改成 CSS 变量就可以做成动态进度条,数据一变,环跟着变,不需要 JS 画 Canvas。
3.4 repeating 系列:条纹和栅格一次搞定
渐变还有一个隐藏玩法:repeating-linear-gradient() 和 repeating-radial-gradient()。它们会把渐变自动重复拼接,适合做一些需要规律图案的背景。
最经典的例子是条纹背景:
css复制background: repeating-linear-gradient(
45deg,
#eee 0px,
#eee 10px,
#fff 10px,
#fff 20px
);
这个背景会在 45 度方向每 20px 重复一次,前 10px 是浅灰,后 10px 是白色,形成斜纹效果。改数字可以控制条纹粗细和间隔,不用依赖图片。
我在管理后台的表格斑马纹、列表分割线、以及一些"骨架屏"的占位背景上都用过 repeating 渐变,效果稳定,而且非常轻量。需要注意的一点是,repeating 渐变和普通渐变一样,色标列表必须严格递增,否则浏览器会忽略无效值。
4. 四个热搜效果完整落地
4.1 字体渐变:background-clip: text 三步实现
"CSS 字体渐变"在热搜里反复出现。想给文字加上渐变效果,核心代码只有三步:给文字设置背景渐变,把背景裁剪到文字区域,把文字本身颜色设为透明。
css复制.gradient-text {
background: linear-gradient(90deg, #ff9a9e, #fad0c4, #a18cd1);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
关键点是 background-clip: text 这个属性,它会把背景裁剪成文字的形状,配合 color: transparent 让文字本身透明,渐变色就"透"出来了。需要提醒的是,虽然 background-clip: text 已经标准化,但 Safari 以及部分老内核浏览器还是需要 -webkit- 前缀,所以实际项目中最好两个都写。
如果想让渐变文字更生动,可以给背景加动画,让背景位置左右移动:
css复制.animated-gradient-text {
background: linear-gradient(90deg, #ff9a9e, #fad0c4, #a18cd1, #ff9a9e);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: shine 3s linear infinite;
}
@keyframes shine {
to { background-position: -200% center; }
}
这里的关键是 background-size 要放大到 200%,动画移动的是背景位置,否则背景占满整个元素后没有位移空间,动画看不出效果。我在活动页的标题、Banner 大字上用过几次,观感提升非常明显。
4.2 涟漪光圈扩散:animation 和 radial-gradient 的配合
热搜里"css涟漪光圈扩散"也是一个高频需求,常用于按钮点击反馈、地图定位点、直播关注按钮。
核心思路:一个带有径向渐变的圆圈,配合 scale 和 opacity 动画实现扩散并淡出。实现时可以用伪元素 + 动画,不增加额外 DOM:
css复制.ripple {
position: relative;
width: 80px;
height: 80px;
border-radius: 50%;
background: radial-gradient(circle, rgba(52,152,219,0.6), rgba(52,152,219,0));
}
.ripple::after {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: inherit;
animation: ripple 2s ease-out infinite;
}
@keyframes ripple {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(2.5); opacity: 0; }
}
这样做的效果是:一个逐渐放大、透明度逐渐降为 0 的光圈从按钮中心向外扩散,视觉上就是"涟漪"。
如果想做多层涟漪,可以再加一个 ::before 伪元素,动画延迟 1 秒,形成两层错开的扩散效果。这个方案我没有用任何 JS,性能上只会多两个合成层动画,浏览器优化得不错。
需要注意,transform: scale 的放大基准是元素的中心点,所以伪元素要 absolute 铺满父元素,并且 inset: 0 保证从同一个圆心扩散。如果伪元素没有和父元素重合,光圈会在一个偏移的位置出现,看起来就很奇怪。
4.3 金光闪闪效果:多角度渐变 + 背景位移的灵活组合
"css如何做出来金光闪闪的效果"在热搜中出现,不是大家闲得无聊,是确实有场景:金币、活动主题、VIP 标识、加载动画。纯 CSS 实现金光闪闪,核心还是背景渐变 + 位移动画,只是角度和颜色需要选得更有"光感"。
我最常用的一个模板:
css复制.shine {
background: linear-gradient(
110deg,
#b8860b 20%,
#ffd700 40%,
#fff8dc 50%,
#ffd700 60%,
#b8860b 80%
);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: goldShine 2.5s linear infinite;
}
@keyframes goldShine {
to { background-position: 200% center; }
}
这里的核心逻辑是:把高光层(白色)放在渐变中间,然后让整个背景左右扫动。因为 background-clip 已经把背景裁剪到文字上,扫动时高光就会在文字表面来回掠过,形成"金光闪闪"的感觉。
调整高光的宽度和速度即可控制闪光频率。想更克制一点,把 background-size 从 200% 改成 300%,动画时长调成 4s,闪光就更柔和。这个技巧做品宣页很实用,但我个人的建议是:金光闪闪属于强装饰效果,密集场景应该谨慎使用,否则会晃眼,影响阅读。
4.4 波浪背景:不用图片也能做出流动感
"css波浪效果"在项目里经常遇到,常见场景有:页面底部装饰、音乐播放器动画、数据面板的分隔带。纯 CSS 实现波浪,最简单的方式是用多个相邻的 border-radius 圆弧拼接,不过这里我更推荐用重复径向渐变来模拟"扇形波浪":
css复制.wave {
height: 80px;
background:
radial-gradient(circle at 10px -5px, transparent 12px, #3498db 13px, #3498db 17px, transparent 18px);
background-size: 30px 20px;
}
这种方式生成的是一排锯齿或半圆弧图案。如果要做动态流动的波浪,通常会用两张不同的渐变图案叠加,然后通过 background-position 动画让它们以不同速度移动,视觉上就形成波浪打过来的效果。
css复制.wave-animated {
height: 120px;
background:
radial-gradient(circle at 8px 8px, transparent 8px, #2e8b57 9px, #2e8b57 14px, transparent 15px),
radial-gradient(circle at 24px 8px, transparent 8px, #3cb371 9px, #3cb371 14px, transparent 15px);
background-size: 32px 32px, 32px 32px;
animation: waveMove 4s linear infinite;
}
@keyframes waveMove {
to { background-position: 32px 0, -32px 0; }
}
两层背景以相反方向周期性位移,就会产生波浪错动的视觉。这个方法比引入 SVG 动画更轻量,不过说实话,如果项目已经用了很多 SVG,SVG 波浪路径的可控性会更好。纯 CSS 适合简单装饰位,精细场景建议配图或 SVG。做波纹时要注意 background-size 和动画位移的数值要匹配,否则移动一个周期后会跳变。
5. 兼容性、性能与踩坑清单
5.1 兼容性速查:哪些项目能放心用新函数
颜色函数和渐变新特性的浏览器兼容性,我根据实际测试整理了一个速查表:
| 特性 | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
| rgb()/hsl() 空格写法 | 支持 | 支持 | 支持 | 支持 |
| hwb() | 支持 | 支持 | 支持 | 支持(需留意版本) |
| lab()/lch() | 支持 | 支持 | 支持 | 支持 |
| color() display-p3 | 支持 | 支持 | 支持 | 部分 |
| color-mix() | 支持 | 支持 | 支持 | 支持 |
| linear/radial/conic-gradient | 支持 | 支持 | 支持 | 支持 |
| background-clip: text | 支持 | 支持 | 支持 | 需 -webkit- 前缀 |
这个表是大概的当前状态,具体项目上线前建议用 caniuse 或者本地多浏览器测试。要注意的一点是:老版本浏览器对空格分隔的 rgb()/hsl() 解析会失败,如果项目要兼容 IE11 或者很旧的 WebView,仍然需要用逗号分隔或者 rgba()/hsla() 老写法。不过带逗号的老写法会慢慢退出历史舞台,新项目不必再纠结。
5.2 渐变使用中的五个经典坑
第一个坑:角度方向记反。linear-gradient 里 0deg 是 to top(从下到上),90deg 是 to right(从左到右),很多人凭直觉以为 90deg 是从上到下,结果出来的方向和设计稿完全相反。我的记忆方法是:角度对应的是"渐变终点方向"而不是起点方向。
第二个坑:透明渐变发灰。比如从半透明白色渐变到透明,如果直接写 linear-gradient(rgba(255,255,255,0.5), transparent),中间过渡区域会明显发灰。原因是浏览器默认在 sRGB 空间里插值,半透明颜色要既变透明度又变最终显示颜色。解决办法是给 transparent 指定一个和起点相同的颜色:linear-gradient(rgba(255,255,255,0.5), rgba(255,255,255,0))。这个问题在叠加阴影、遮罩时经常遇到,颜色带上一眼就能发现。
第三个坑:渐变多边形边缘出现锯齿。尤其 radial-gradient 做光晕时,如果色标之间的过渡太硬,边缘会有难看的锯齿或色带。解决方法是让最后一个色标渐变距离足够宽,或者给 background 加上多一层更柔和的渐变来过渡。
第四个坑:百分比和像素混用的顺序。比如 linear-gradient(90deg, #fff 0%, #000 50%, #f00 50%, #f00 100%),这里 50% 和 50% 放在一起是为了硬分割,但如果不小心写成了 50% 和 55%,中间会多出一条渐变色带。要硬分割,两个色标的位置值必须一致,不要差一点点。
第五个坑:动画渐变性能。给 background-image 做动画不是好主意,因为背景图的渐变计算成本较高,在低端机上会掉帧。正确做法是:让渐变放在一个独立元素上,通过 transform 或 opacity 做动画。比如光效层用 transform: translateX() 移动,而不是 background-position,性能会稳定很多。
5.3 性能优化与团队协作建议
关于渐变的性能,很多人有一个误区:以为渐变越多越慢。实际上渐变的计算通常是在合成阶段一次性完成的,关键不在于"用了几个渐变",而在于"是否频繁触发重绘和合成"。常见的原则是:静态渐变随便用,动画渐变尽量用 transform/opacity,避免动 background-position、background-image 这类属性。
团队协作方面,我建议把颜色和渐变统一收敛到变量里:
css复制:root {
--brand: #3498db;
--brand-light: color-mix(in srgb, var(--brand), white 20%);
--brand-dark: color-mix(in srgb, var(--brand), black 20%);
--grad-primary: linear-gradient(135deg, var(--brand-light), var(--brand));
}
这样做的好处是:UI 调整主色时,所有依赖 --brand 的衍生色和渐变自动联动,不用全局替换。"原子化 CSS"和"样式一致性"这两个热搜关键词,实际上靠这套变量体系就能解决大部分问题,不需要迷信某个框架。
还有一个小经验:做多主题或暗色模式时,尽量避免在组件里写死十六进制颜色。把颜色全部收敛到变量,然后通过按钮切换 :root 或 html 上的 data-theme 属性,整套界面的配色就能瞬间切换。颜色函数和变量的组合,是现在前端工程化里性价比最高的配色方案。
用了几年颜色函数之后,我自己的习惯已经变成:常规页面一律 hsl 调色,设计系统用 hwb 或 lch 生成色阶,颜色混合全部交给 color-mix(),渐变做装饰、做动效、做图标,尽量不依赖图片资源。刚开始从十六进制切到函数式颜色会有一点点不适应,但只要你试上一周,基本就回不去了。
最后分享一个我踩过多次的坑。做渐变背景时,如果你发现颜色过渡带了奇怪的灰,先别怀疑设计稿,想想 transparent 的插值问题;如果发现背景动画一卡一卡的,先别加 transform,先把 background-position 动画换成 transform 动画。这些问题排查久了,你对颜色和渐变的掌控感会明显提升,写出来的界面自然就更"绚丽"了。
