渐变是 CSS 里最“便宜”的视觉资产:它不需要图片、不需要网络请求、不需要额外的 DOM,却能撑起整个页面的气质。但绝大多数人只会写 linear-gradient(#fff, #000),然后用一张 800KB 的背景图去实现本该由几十个字符完成的效果。复杂渐变背景的真正难点,从来不是“会不会写语法”,而是“能不能把光、色、层、纹理组织成一个统一的视觉系统”。这篇 50 分钟长文,从渐变的数学本质讲起,穿过角度与色标、径向与锥形、重复图案、多重背景、现代色彩空间、色带与噪点、渐变文字与渐变边框,最后落到性能、动画、兼容与调优。文中配有 6 个可实时操作的实验室,建议你边读边改参数——那比读十遍文章都管用。
渐变到底是什么:一张被数学描述的颜色地图
先建立一个正确的心理模型。渐变不是“把两个颜色混在一起”,而是一张被参数化的颜色地图(color map)。浏览器在渲染时,会为元素内部的每一个像素计算一个“进度值” t(0 到 1),然后按照你定义的颜色节点,把这个 t 映射成一个具体的颜色。
所以,写渐变的本质是回答三个问题:
- 进度从哪里来?——由渐变类型决定。
linear-gradient沿一条直线计算,radial-gradient沿半径计算,conic-gradient沿角度计算。 - 颜色节点怎么排?——由色标(color stop)列表决定。每个色标是“某个位置 + 某个颜色”。
- 相邻颜色怎么插值?——由插值色彩空间决定,默认 sRGB,也可以指定
in oklch、in oklab等。
把这三件事拆开理解,你会发现所谓“复杂渐变”,其实只是这三个维度上的组合爆炸:线性 + 6 个色标 + oklch 插值,就是一种效果;径向 + 硬边色标 + 多重叠加,又是另一种效果。
.a {
background-image: linear-gradient(#2563eb, #ec4899);
}
/* 加方向:角度决定“进度从哪里来” */
.b {
background-image: linear-gradient(135deg, #2563eb, #ec4899);
}
/* 加中间色标:控制过渡的节奏 */
.c {
background-image: linear-gradient(135deg,
#2563eb 0%, #8b5cf6 45%, #ec4899 100%);
}
/* 加硬边:色标位置相同 → 突变 */
.d {
background-image: linear-gradient(90deg,
#2563eb 0 50%, #ec4899 50% 100%);
}
/* 加色彩空间:改变插值路径 */
.e {
background-image: linear-gradient(in oklch, #2563eb, #ec4899);
}
为什么“背景图”应该被渐变取代
- 体积:一段 120 字符的渐变,等价于一张可能 200KB 的 PNG。
- 清晰度:渐变是矢量计算,任何 DPR、任何缩放都绝对清晰。
- 可维护:改品牌色只需换一个变量,不用重新导出素材。
- 可响应:渐变可以随容器尺寸、主题模式、媒体查询实时变化。
- 可动画:配合
@property与现代插值,渐变本身就能动起来。
当然,渐变不是万能的。真实的照片纹理、复杂的插画、有机的噪点笔触,依然需要图片。但“背景氛围层”这一类需求,90% 都可以并且应该用渐变实现。
linear-gradient 深度解析:角度、色标与硬边
线性渐变是最常用的一种。它的语法是:
角度到底怎么算的
这是最容易出错的地方:CSS 渐变的角度是“顺时针、从正上方开始计算”,而不是数学上从正右方开始。
0deg:从下往上(渐变线指向正上)90deg:从左往右180deg:从上往下(默认值)270deg:从右往左45deg:从左下到右上
注意一个关键细节:渐变线的长度会被自动拉伸到恰好覆盖整个元素。也就是说,在非正方形元素上,45deg 的渐变线不会真的穿过对角线,而是穿过一个经过计算的长度,保证起点与终点颜色不缺失。这就是为什么你在宽高比不同的元素上写同样的 45deg,看起来“斜度”却不同。
拖动角度滑块时,请特别留意 0°、90°、180°、270° 这四个位置——它们是理解角度的锚点。同时观察中间色标从 50% 移到 20% 或 80% 时,颜色的“重心”如何整体偏移。控制色标位置,就是控制视觉重心。
硬边:渐变最被低估的能力
当两个相邻色标位于同一位置时,浏览器会画出一条锐利的边界。这意味着你可以用渐变画出纯色块、条纹、旗帜、棋盘格,甚至简化版的插画。
.split {
background-image: linear-gradient(90deg, #2563eb 0 50%, #ec4899 50% 100%);
}
/* 三段竖向条纹 */
.tricolor {
background-image: linear-gradient(90deg,
#1d4ed8 0 33.33%, #f8fafc 33.33% 66.66%, #dc2626 66.66% 100%);
}
/* 斜向条纹 */
.stripes {
background-image: repeating-linear-gradient(
45deg, #2563eb 0 12px, #3b82f6 12px 24px);
}
/* 锐利渐变 + 柔和渐变混用,制造“光带” */
.lightband {
background-image: linear-gradient(115deg,
#0f172a 0 30%, #1e40af 30% 55%, #38bdf8 55% 58%, #0f172a 58% 100%);
}
这种“硬边 + 柔边”混用的手法,是做出高级感渐变背景的关键。纯柔和的渐变容易显得平淡甚至“脏”;加入几条锐利的光带、分割线、切面,画面立刻就有结构了。
radial-gradient 深度解析:光从哪里来
径向渐变负责回答“光从哪里来”。它比线性渐变多了一组参数:形状、尺寸、圆心位置。
[ 形状 || 尺寸 ]? [ at 位置 ]? , 色标1 , 色标2 , ... );
形状与尺寸
- 形状:
circle(圆)或ellipse(椭圆,默认)。 - 尺寸关键字:
closest-side(默认)、farthest-side、closest-corner、farthest-corner。 - 显式尺寸:如
circle 200px at 30% 30%,或ellipse 60% 40% at center。
尺寸关键字是最容易被忽略、却最影响效果的部分。默认的 farthest-corner 会让渐变铺满整个元素的对角方向,而 closest-side 会让渐变在最近的一条边处结束——后者常常能制造出“聚光灯从边缘打进来”的效果。
经典用法:多层光晕叠加
单个径向渐变往往不够。真正有质感的光效,通常由 2~3 个不同位置、不同颜色的径向渐变叠加而成——就像摄影棚里的主光、辅光与轮廓光。
.hero-light {
background-color: #0b1220;
background-image:
radial-gradient(circle at 20% 25%, rgba(59,130,246,0.55), transparent 42%),
radial-gradient(circle at 78% 30%, rgba(236,72,153,0.45), transparent 45%),
radial-gradient(ellipse at 50% 100%, rgba(16,185,129,0.35), transparent 55%);
}
/* 聚光灯:用 closest-side 制造边缘收束 */
.spotlight {
background-image: radial-gradient(circle closest-side at 50% 0%,
rgba(255,255,255,0.9), rgba(255,255,255,0) 100%);
}
/* 柔和光斑:用极低透明度 + 大半径 */
.glow {
background-image: radial-gradient(circle at 50% 50%,
rgba(96,165,250,0.35), rgba(96,165,250,0) 70%);
}
这里有一个非常重要的技巧:用 transparent 与用 rgba(...,0) 并不完全等价。transparent 在多数浏览器里等价于 rgba(0,0,0,0),也就是“透明的黑色”。如果你从红色渐变到 transparent,中间会经过一段“发暗”的过渡,因为插值把黑色也算进去了。想要干净的淡出,应该写成 rgba(255,0,0,0)——即透明但颜色相同。
radial-gradient(circle, #3b82f6, transparent)终点其实是“透明黑”,过渡带会明显发暗、发脏。
radial-gradient(circle, #3b82f6, rgba(59,130,246,0))保持色相一致,只让透明度下降,过渡干净通透。
conic-gradient 深度解析:被严重低估的第四种渐变
锥形渐变的进度不是沿直线或半径推进,而是绕着一个圆心按角度旋转。这一特性让它天然适合做:饼图、进度环、色轮、雷达扫描、光泽旋转、以及各种“扇形分割”的装饰效果。
[ from 起始角度 ]? [ at 圆心位置 ]? , 色标1 , 色标2 , ... );
一个完整的进度环
用 conic-gradient 加一个内层遮罩,就能做出纯 CSS 的进度环——不需要 SVG,不需要 canvas。
实现思路非常直接:外层是一个圆,背景是锥形渐变;内层用 ::before 画一个白色的圆,靠 inset 留出环的宽度。数值变化时只改两个百分比,浏览器重绘的区域很小,性能非常好。
饼图、色轮与扇形分割
.pie {
background-image: conic-gradient(
#2563eb 0 33.33%,
#8b5cf6 33.33% 66.66%,
#ec4899 66.66% 100%);
border-radius: 50%;
}
/* 色轮:用多个色标均匀分布 */
.wheel {
background-image: conic-gradient(in oklch,
#ef4444, #f59e0b, #eab308, #10b981,
#06b6d4, #3b82f6, #8b5cf6, #ef4444);
border-radius: 50%;
}
/* 旋转光泽(做 loading 或高光扫过) */
.shine {
background-image: conic-gradient(from 0deg,
transparent 0deg 300deg,
rgba(255,255,255,0.75) 340deg,
transparent 360deg);
}
/* 雷达扫描线:配合 rotation 动画 */
.radar {
background-image: conic-gradient(from 0deg,
rgba(16,185,129,0.85), rgba(16,185,129,0) 55deg, transparent 360deg);
}
锥形渐变有一个非常实用的细节:用 from 关键字可以指定起始角度,并且支持负角度与大于 360° 的角度。这在做“从某个特定方向开始扫”的效果时特别方便。
另一个技巧是:锥形渐变配合 mask 能做出极其精细的仪表盘。用锥形渐变做刻度,用遮罩裁剪出圆弧,再叠加一层径向渐变做内阴影,一个工业风的仪表盘就完成了——整个过程没有一张图片。
repeating-* 与图案生成:用渐变画出纹理
三类渐变都有一个 repeating- 版本:repeating-linear-gradient、repeating-radial-gradient、repeating-conic-gradient。它们把渐变的色标区间按固定长度无限重复,从而把“一段过渡”变成“一种纹理”。
亲手做一个网格背景
“网格线背景”是后台系统、编辑器、画布类产品里最常见的装饰。它的实现只需要两层线性渐变加一个 background-size。
注意这里的关键点:渐变中的线宽如果写成 0,是不会渲染的;必须至少 1px(或者使用小数如 0.5px 在高 DPR 屏上)。同时,为了让线条落在像素网格上,建议让 background-size 是整数,避免出现半像素模糊。
棋盘格的正确写法
.checker {
background-color: #f8fafc;
background-image:
linear-gradient(45deg, #cbd5e1 25%, transparent 25% 75%, #cbd5e1 75%),
linear-gradient(45deg, #cbd5e1 25%, transparent 25% 75%, #cbd5e1 75%);
background-size: 24px 24px;
background-position: 0 0, 12px 12px;
}
/* 更简洁的写法:用 repeating-conic-gradient */
.checker-2 {
background-image: repeating-conic-gradient(#cbd5e1 0 25%, #f8fafc 0 50%);
background-size: 24px 24px;
}
第二种写法更短,也更易读。repeating-conic-gradient 把整个平面分割成 90° 的扇形,再用 background-size 限制重复区域,天然就得到了棋盘格。这是现代 CSS 里非常优雅的一个技巧。
图案的性能提醒
- 图案的重复次数越多,绘制开销越大。一个 8px 的网格铺满 4K 屏,意味着数十万个重复单元。
- 把图案放在独立图层上,或者用
contain: paint限制它的影响范围。 - 避免在滚动容器上使用大面积高密度图案,它会让滚动时的绘制成本显著上升。
- 固定装饰图案优先用
background-attachment: fixed的反面——固定附件在某些浏览器上会触发整页重绘,通常得不偿失。
多重背景叠加:复杂感的真正来源
单一渐变再漂亮,也很难撑起“复杂”二字。真正的高级感来自层与层的叠加:底层铺色、中层布光、上层加纹理、顶层做点缀。
CSS 允许在 background-image 中写多个渐变,用逗号分隔。排在前面的层在上方,排在后面的层在下方。而 background-size、background-position、background-repeat 也可以写多个值,与前面的层一一对应。
.complex-bg {
background-color: #0b1220;
background-image:
/* 第 1 层:顶部高光 */
linear-gradient(180deg, rgba(255,255,255,0.10), transparent 32%),
/* 第 2 层:细网格纹理 */
linear-gradient(to right, rgba(148,163,184,0.10) 1px, transparent 1px),
linear-gradient(to bottom, rgba(148,163,184,0.10) 1px, transparent 1px),
/* 第 3 层:主光源 */
radial-gradient(circle at 18% 20%, rgba(59,130,246,0.45), transparent 45%),
/* 第 4 层:辅光源 */
radial-gradient(circle at 85% 75%, rgba(236,72,153,0.38), transparent 48%);
background-size:
100% 100%,
26px 26px,
26px 26px,
100% 100%,
100% 100%;
}
这里最容易出错的地方是 background-size 的顺序对应。如果层数与 size 数量不一致,浏览器会循环取值,导致某些层尺寸错乱。写复杂背景时,建议把每一层的注释写清楚,保持缩进对齐。
.hero {
background:
radial-gradient(circle at 25% 20%,
rgba(96,165,250,0.5), transparent 45%) 0 0 / 100% 100% no-repeat,
linear-gradient(140deg, #0f172a, #1e3a8a) 0 0 / 100% 100% no-repeat;
}
/* 简写格式:
/* 注意:简写会重置所有 background-* 属性 */
/* 混用时务必写全,否则容易覆盖掉之前的设置 */
叠加模式:让层与层真正“融合”
单纯地叠加上去,上层会遮住下层。想要真正融合,需要借助混合模式:background-blend-mode(层与层之间)与 mix-blend-mode(元素与背景之间)。
.blend {
background-image:
repeating-linear-gradient(45deg, rgba(255,255,255,0.6) 0 10px, transparent 10px 20px),
linear-gradient(135deg, #2563eb, #ec4899);
background-blend-mode: overlay, normal;
}
/* 常用混合模式一览 */
/* normal 默认,直接覆盖 */
/* multiply 正片叠底,变暗,适合做阴影与纹理 */
/* screen 滤色,变亮,适合做光晕 */
/* overlay 叠加,增强对比,适合做质感 */
/* soft-light 柔光,最自然的融合 */
/* color-dodge 颜色减淡,强烈的发光感 */
混合模式是复杂渐变背景的“隐藏关卡”。掌握它之后,你会发现同样的两层渐变,加上一句 background-blend-mode: overlay,质感会提升一个档次。但要注意:混合模式会强制浏览器进行额外的像素运算,在移动端大面积使用时需要实测。
现代色彩空间:为什么你的渐变会“发灰”
这是近年 CSS 渐变最重要的变化之一。默认情况下,CSS 在 sRGB 色彩空间里做插值。sRGB 的问题是:它是非线性的、且色域较窄。这导致两个后果——
- 过渡会“发灰”:从蓝色到黄色的中间,会经过一段浑浊的灰绿色。
- 亮度不均匀:从深紫到亮黄的过渡中,中间会出现明显的亮度塌陷。
现代 CSS 允许你指定插值色彩空间:
.srgb {
background-image: linear-gradient(90deg, #2563eb, #facc15);
}
/* oklab / oklch:感知均匀,过渡更干净 */
.ok {
background-image: linear-gradient(in oklab 90deg, #2563eb, #facc15);
}
/* 通过长弧插值绕过色轮(hue interpolation) */
.longer {
background-image: linear-gradient(in hsl longer hue, #2563eb, #facc15);
}
/* 对比:短弧 / 长弧 / 递增色相 */
/* shorter hue(默认)—— 走最短路径 */
/* longer hue —— 绕远路,色彩更丰富 */
/* increasing hue —— 强制色相递增 */
/* decreasing hue —— 强制色相递减 */
如果你观察上面四条色带,会发现第二条(oklab)明显更“通透”,中间不会出现浑浊的灰绿;第三条(hsl longer hue)则像彩虹一样绕了一大圈,色彩最丰富但也最“吵”。没有绝对正确的选择,只有适合场景的选择。
色带(Banding):另一个常见问题
在大面积、低对比度的渐变上,你可能会看到一圈一圈的“色带”。这不是 bug,而是 8 位色深(每通道 256 级)的物理限制——当颜色的变化小于 1/256 时,浏览器只能就近取整。
解决办法有三种:
- 加噪点:在被压缩的色阶上撒一点随机噪点,人眼会把它感知为平滑过渡。
- 改用 oklab / oklch 插值:感知均匀的路径往往能减少可见阶梯。
- 缩小渐变跨度:避免在极大面积上做极缓慢的颜色变化。
上面的噪点层用的是一个内联的 SVG feTurbulence 滤镜,以 data URI 的形式作为背景图,配合 mix-blend-mode: overlay。它的体积只有几百字节,却能让整个渐变背景的质感提升一大截——这是现代 UI 里最划算的一条视觉优化。
渐变的高级用法:文字、边框、遮罩与玻璃
渐变不只能做背景。一旦理解“渐变是一张颜色地图”,你就会发现它可以被用在任何接受图像的地方。
padding: 1px 让渐变只露出一圈。配合 background-size 与动画,就能得到流动的描边。
backdrop-filter: blur()。关键点是背景本身必须有足够的色彩层次,否则模糊之后就只剩一片灰。
渐变文字的正确写法
.grad-text {
background-image: linear-gradient(92deg, #2563eb, #8b5cf6, #ec4899);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/* 让渐变动起来:加大 background-size 再位移 */
.grad-text-flow {
background-size: 220% 100%;
animation: textFlow 6s ease-in-out infinite alternate;
}
@keyframes textFlow {
from { background-position: 0% 50%; }
to { background-position: 100% 50%; }
}
/* 注意:不要忘记 color: transparent 的降级 */
/* 部分老旧环境不支持 background-clip: text */
@supports (background-clip: text) {
.grad-text { color: transparent; }
}
渐变边框的三种实现
- padding + 内层遮罩:外层渐变 + 内层纯色,兼容性最好,圆角需要手动对齐。
border-image:语法简单,但不支持圆角,且不能与border-radius共存。mask组合:用两层遮罩做差集,可以做到任意圆角与不规则形状,是能力最强的方案。
.grad-border-mask {
position: relative;
border-radius: 12px;
background: linear-gradient(120deg, #2563eb, #ec4899);
padding: 1.5px;
}
.grad-border-mask > .inner {
background: white;
border-radius: 11px;
padding: 16px;
}
/* 遮罩方案:边框宽度可以是非整数 */
.masked {
background: linear-gradient(120deg, #2563eb, #ec4899);
padding: 1.5px;
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
渐变遮罩(mask-image 配合渐变)还有一个极其常用的场景:让内容在容器边缘渐隐。比如横向滚动列表,在两端加上 mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent),内容就会自然地“淡出”到边缘之外,比任何渐变覆盖层都干净,因为它连背景一起处理了。
交互实验室:实时渐变构建器
下面这个小工具把渐变类型、角度、颜色、插值色彩空间全部暴露出来。改动任意参数,预览与代码同步更新——这是熟悉渐变语法最快的方式。
建议做几组对比实验:
- 固定两个颜色,只切换插值色彩空间,观察中间过渡的“干净程度”;
- 把类型从 linear 切到 conic,看看同一组颜色如何变成环形分布;
- 把三个颜色改成同一色相的不同明度,做出一套“单色系”的高级背景。
渐变的性能:哪些能动画,哪些不能
渐变看起来只是一层“颜色”,但它的渲染代价并不为零。理解代价来源,才能在做复杂背景时心里有底。
| 操作 | 渲染代价 | 说明 | 建议 |
|---|---|---|---|
静态渐变背景 |
低 | 只绘制一次,之后参与合成 | 放心使用 |
transform 动画中的渐变元素 |
低 | 渐变作为图层内容被整体移动 | 首选方案 |
opacity 动画中的渐变元素 |
低 | 只改变图层透明度 | 首选方案 |
动画 background-position |
中 | 需要重新绘制渐变区域 | 小面积可用,大面积慎用 |
| 动画渐变本身的色标 | 高 | 每帧重新计算整张颜色地图并重绘 | 用 @property 做小范围可控动画 |
| 大面积高密度重复图案 | 中 | 重复单元数量决定绘制成本 | 限制范围或降低密度 |
backdrop-filter + 渐变 |
高 | 需要采样背景并做模糊运算 | 避免大面积长时存在 |
动态渐变的两种实现,性能天差地别
“流动的渐变背景”是最常见的动态视觉效果。它有两种实现思路:动画 background-position,或者把渐变画在超宽的容器上、用 transform 平移。
在元素数量少、面积小的时候,两者的帧率可能都是 60。但当这个背景铺满整个 Hero 区域、并且页面上还有其他动画在跑时,第一种方案会持续占用主线程的绘制时间,第二种则几乎不占用。这就是为什么“用 transform 替代 background-position”是动态渐变的第一原则。
用 @property 让渐变真正“动”起来
CSS 的 @property 可以为自定义属性声明类型,使它可以被动画平滑插值。这意味着我们可以动画渐变的色标位置、角度,甚至颜色本身。
@property --grad-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --grad-stop {
syntax: '<percentage>';
inherits: false;
initial-value: 50%;
}
/* 用变量组装渐变 */
.spin-bg {
--grad-angle: 0deg;
--grad-stop: 50%;
background-image: linear-gradient(
var(--grad-angle),
#2563eb 0,
#ec4899 var(--grad-stop),
#2563eb 100%);
animation: spinGrad 5s linear infinite;
}
@keyframes spinGrad {
to { --grad-angle: 360deg; }
}
/* 注意:@property 动画在部分浏览器上仍需实测 */
/* 大面积使用时建议降级为 transform 方案 */
@property 打开的是一扇新门:渐变不再是“死”的图片,而是可以被平滑驱动的参数系统。但要清醒地认识到——每一帧都在重新计算整张颜色地图,代价远高于 transform。它适合小面积、点睛式的效果,不适合铺满全屏长时间运行。
色带、断层与脏色:三个最常见的渐变病
病一:色带(Banding)
表现为大面积的同心圆或平行条纹。原因在上一节已经解释过,是色深限制。解决手段按性价比排序:
- 加一层极低透明度的噪点(成本最低,效果最好)。
- 改用
in oklab/in oklch插值。 - 缩短渐变的跨度,或增加中间色标让过渡更“陡”一些。
病二:断层(Hard Stop)
表现为本该柔和的过渡中出现了明显的折线。这通常是因为相邻色标的位置被写成了相同的值,或者两个色标的颜色差异过大但位置太近。
linear-gradient(90deg, #2563eb 0%, #ec4899 0%, #ec4899 100%)前两个色标位置相同,第一段渐变被压缩为 0 宽度,视觉上是一道生硬的切口。
linear-gradient(90deg, #2563eb 0%, #2563eb 40%, #ec4899 40%, #ec4899 100%)先明确“色块”的范围,再让边界锐利,意图清晰且易于维护。
病三:脏色(Muddy Middle)
表现为两个鲜艳颜色之间出现一段灰暗、浑浊的过渡。这是 sRGB 线性插值的典型问题。解决办法:
- 指定
in oklab或in oklch插值; - 在中间手动插入一个高饱和度的中间色标(例如从蓝到黄,中间插一个青色);
- 缩短两者的距离,让过渡带变窄,减少“脏”的暴露面积。
/* ❌ 中间发灰 */
.a { background: linear-gradient(90deg, #1d4ed8, #facc15); }
/* ✅ 方案一:换插值空间 */
.b { background: linear-gradient(in oklab 90deg, #1d4ed8, #facc15); }
/* ✅ 方案二:手工插入高饱和中间色 */
.c { background: linear-gradient(90deg, #1d4ed8, #06b6d4 50%, #facc15); }
/* ✅ 方案三:用 longer hue 绕色轮 */
.d { background: linear-gradient(in hsl longer hue 90deg, #1d4ed8, #facc15); }
调试渐变的小技巧
- 临时换成高对比色:把渐变的两个端点换成红与绿,能立刻看清渐变线方向与覆盖范围。
- 用 DevTools 的渐变编辑器:点击样式面板中渐变旁边的小方块,可以可视化拖拽色标。
- 加一层网格线辅助:临时叠加
repeating-linear-gradient网格,能看清background-size与position的实际效果。 - 逐个禁用图层:多层背景出问题时,把
background-image临时拆成一条一条注释,快速定位是哪一层出错。
实战:四张卡片的渐变配方
理论说完了,来看几组可以直接抄进项目的“配方”。它们都只用渐变与纯 CSS,没有任何图片资源。
.aurora {
background-image:
radial-gradient(circle at 15% 20%, rgba(255,255,255,0.35), transparent 42%),
linear-gradient(140deg, #1d4ed8, #7c3aed 70%);
}
/* 配方二:虹彩 */
.iridescent {
background-image:
radial-gradient(circle at 80% 15%, rgba(255,255,255,0.30), transparent 40%),
conic-gradient(from 210deg at 50% 50%,
#0891b2, #22d3ee, #2563eb, #0891b2);
}
/* 配方三:光栅 */
.raster {
background-image:
linear-gradient(120deg,
rgba(255,255,255,0.14) 0 25%, transparent 25% 50%,
rgba(255,255,255,0.14) 50% 75%, transparent 75% 100%),
linear-gradient(135deg, #db2777, #f97316);
background-size: 28px 28px, 100% 100%;
}
/* 配方四:碳纤 */
.carbon {
background-image:
repeating-linear-gradient(45deg,
rgba(255,255,255,0.12) 0 8px, transparent 8px 16px),
linear-gradient(160deg, #047857, #10b981);
}
/* 想更进一步?加一层噪点与混合模式 */
.aurora::after,
.iridescent::after {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,...");
mix-blend-mode: overlay;
opacity: 0.5;
}
如何系统地设计一套渐变体系
- 先定色相家族:主色、辅助色、强调色,各挑 1~2 个色相。
- 再定明度梯度:为每个色相准备 3~4 级明度,作为渐变的两端与中间节点。
- 统一方向语言:整套产品里,光从哪个方向来?统一成 135° 或 160°,画面会立刻统一。
- 统一纹理密度:网格 24px、条纹 12px——把重复图案的节奏定下来,避免每条卡片都不一样。
- 抽成 CSS 变量:把颜色与角度都变量化,换肤时只改一处。
:root {
/* 方向 */
--grad-angle: 135deg;
--grad-angle-soft: 160deg;
/* 色板 */
--c-blue: #2563eb;
--c-violet: #8b5cf6;
--c-pink: #ec4899;
--c-cyan: #06b6d4;
--c-ink: #0b1220;
/* 组合好的渐变 */
--grad-primary: linear-gradient(var(--grad-angle), var(--c-blue), var(--c-violet));
--grad-vivid: linear-gradient(var(--grad-angle), var(--c-violet), var(--c-pink));
--grad-ink: linear-gradient(var(--grad-angle-soft), var(--c-ink), #1e3a8a);
}
/* 使用时只需要引用 */
.btn-primary {
background-image: var(--grad-primary);
}
.card-accent {
background-image: var(--grad-vivid);
}
兼容性、降级与可访问性
兼容性现状
/* linear / radial / conic 渐变 —— 全平台,无需前缀 */
/* repeating-* 渐变 —— 全平台 */
/* background-blend-mode —— Chrome 35+ / Safari 8+ */
/* background-clip: text —— Chrome 120+ 无需前缀 */
/* 渐变插值色彩空间 —— Chrome 111+ / Safari 16.4+ */
/* @property —— Chrome 85+ / Safari 16.4+ */
/* color-mix() —— Chrome 111+ / Safari 16.2+ */
/* backdrop-filter —— Chrome 76+ / Safari 9+(需前缀) */
/* 关键:给新特性加 @supports 守卫 */
@supports (background: linear-gradient(in oklab, red, blue)) {
.hero {
background-image: linear-gradient(in oklab 135deg, #1d4ed8, #ec4899);
}
}
/* 基础声明放前面,增强声明放后面 */
.hero {
background-image: linear-gradient(135deg, #1d4ed8, #ec4899);
background-image: linear-gradient(in oklab 135deg, #1d4ed8, #ec4899);
}
可访问性:渐变也要讲对比度
渐变背景上的文字,是 WCAG 对比度问题的高发区。因为背景颜色是变化的,文字在渐变的某一段上可能达标,在另一段上就不达标。
- 取最坏情况:把文字放在渐变中最亮(或最暗)的位置,测量那一处的对比度。
- 加一层压暗/提亮遮罩:在渐变上再叠一层
linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)),让整体明度可控。 - 给文字加文本阴影:
text-shadow: 0 1px 8px rgba(0,0,0,0.5)能显著提升可读性。 - 避免在渐变上放小字号细体:对比度不足时,小字最先“消失”。
- 支持
prefers-contrast:为高对比度偏好提供纯色背景降级。
.hero-title {
color: #ffffff;
text-shadow: 0 1px 10px rgba(2, 6, 23, 0.55);
}
/* 高对比度偏好下降级为纯色 */
@media (prefers-contrast: more) {
.hero {
background-image: none;
background-color: #0b1220;
}
}
/* 打印时把渐变换成白底,省墨又清晰 */
@media print {
.hero {
background: #ffffff;
color: #0f172a;
}
}
复杂渐变背景最佳实践清单
把全文的结论浓缩成一份可以直接贴在工位上的清单。
- 优先用渐变替代背景图:体积、清晰度、可维护性全面占优。
- 先把三个问题想清楚:进度从哪来、色标怎么排、用什么色彩空间插值。
- 记住 CSS 角度从正上方顺时针计算:0° 向上、90° 向右、180° 向下。
- 用硬边色标做结构:纯柔和渐变容易平淡,加入锐利切面立刻有层次。
- 避免用
transparent做淡出:改用同色相的rgba(...,0),防止过渡发灰。 - 多层叠加控制在 3~6 层:底层铺色、中层布光、上层纹理,够用即可。
- background-size 与层数一一对应:数量不匹配会导致尺寸错乱。
- 善用
background-blend-mode:一句overlay就能让质感翻倍。 - 大面积渐变加噪点:这是消除色带最划算的手段。
- 跨色相渐变改用 oklab / oklch:避免中间发灰发脏。
- 动态渐变用 transform 而非 background-position:把重绘变成合成。
- 渐变文字记得写降级:
@supports (background-clip: text)守卫color: transparent。 - 渐变上的文字要测对比度:取最坏情况,必要时加压暗层与阴影。
- 新特性一律渐进增强:基础声明在前,增强声明在后,加
@supports守卫。 - 抽成设计令牌:角度、色板、纹理密度都变量化,整套 UI 才能统一。
- 用 DevTools 渐变编辑器调色标:可视化拖拽比反复改代码快十倍。
/* 1. 设计令牌 */
:root {
--grad-angle: 135deg;
--grad-angle-soft: 160deg;
--c-blue: #2563eb;
--c-violet: #8b5cf6;
--c-pink: #ec4899;
--c-ink: #0b1220;
--grad-primary: linear-gradient(var(--grad-angle), var(--c-blue), var(--c-violet));
--grad-vivid: linear-gradient(var(--grad-angle), var(--c-violet), var(--c-pink));
--grad-ink: linear-gradient(var(--grad-angle-soft), var(--c-ink), #1e3a8a);
}
/* 2. 通用的氛围背景 */
.surface-glow {
position: relative;
background-color: var(--c-ink);
background-image:
radial-gradient(circle at 18% 22%, rgba(59,130,246,0.45), transparent 45%),
radial-gradient(circle at 82% 74%, rgba(236,72,153,0.38), transparent 48%);
isolation: isolate;
}
/* 3. 细网格纹理层 */
.surface-grid::before {
content: '';
position: absolute;
inset: 0;
background-image:
linear-gradient(to right, rgba(148,163,184,0.10) 1px, transparent 1px),
linear-gradient(to bottom, rgba(148,163,184,0.10) 1px, transparent 1px);
background-size: 26px 26px;
pointer-events: none;
}
/* 4. 噪点层(消除色带) */
.surface-noise::after {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,...");
mix-blend-mode: overlay;
opacity: 0.5;
pointer-events: none;
}
/* 5. 渐变动效(只走合成) */
.grad-flow {
overflow: hidden;
}
.grad-flow > .layer {
position: absolute;
inset: 0 -100% 0 0;
background-image: linear-gradient(90deg, var(--c-blue), var(--c-violet), var(--c-pink), var(--c-blue));
animation: gradFlow 8s linear infinite;
}
@keyframes gradFlow {
to { transform: translateX(-50%); }
}
/* 6. 降级与无障碍 */
@media (prefers-reduced-motion: reduce) {
.grad-flow > .layer { animation: none; }
}
@media (prefers-contrast: more) {
.surface-glow {
background-image: none;
background-color: #0b1220;
}
}
渐变之所以值得花这么长的篇幅,是因为它是“以极低成本换取极高视觉回报”的少数手段之一。一个熟练使用渐变的开发者,可以用几十行 CSS 做出别人要加载三张图片才能达到的效果,而且更快、更清晰、更好维护。
但请记住:渐变是工具,不是目的。真正优秀的背景设计,是让人注意到内容,而不是注意到背景。当你做的渐变足够克制、层次足够清晰、在暗色与亮色环境下都成立、在低端机上依然稳定 60fps 时,用户不会夸你的渐变好看——他们只会觉得这个页面“很舒服”。那才是最高的评价。