复杂渐变背景实现

张玥 2026年9月18日 阅读时间 50分钟
CSS渐变 视觉设计 色彩空间 背景图案 性能优化
CSS高级技巧视觉效果之复杂渐变背景实现

渐变是 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);
}
方向 角度 / to right / at 位置
色标 位置 + 颜色,可以省略位置自动均分
插值 sRGB / oklab / oklch / hsl / srgb-linear
叠加 逗号分隔的多个背景层,第一个在最上面

为什么“背景图”应该被渐变取代

  • 体积:一段 120 字符的渐变,等价于一张可能 200KB 的 PNG。
  • 清晰度:渐变是矢量计算,任何 DPR、任何缩放都绝对清晰。
  • 可维护:改品牌色只需换一个变量,不用重新导出素材。
  • 可响应:渐变可以随容器尺寸、主题模式、媒体查询实时变化。
  • 可动画:配合 @property 与现代插值,渐变本身就能动起来。

当然,渐变不是万能的。真实的照片纹理、复杂的插画、有机的噪点笔触,依然需要图片。但“背景氛围层”这一类需求,90% 都可以并且应该用渐变实现。

linear-gradient 深度解析:角度、色标与硬边

线性渐变是最常用的一种。它的语法是:

background-image: linear-gradient( [ 角度 | to 方向 ]? , 色标1 , 色标2 , ... );

角度到底怎么算的

这是最容易出错的地方: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 深度解析:光从哪里来

径向渐变负责回答“光从哪里来”。它比线性渐变多了一组参数:形状、尺寸、圆心位置。

background-image: 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 深度解析:被严重低估的第四种渐变

锥形渐变的进度不是沿直线或半径推进,而是绕着一个圆心按角度旋转。这一特性让它天然适合做:饼图、进度环、色轮、雷达扫描、光泽旋转、以及各种“扇形分割”的装饰效果。

background-image: conic-gradient(
  [ from 起始角度 ]? [ at 圆心位置 ]? , 色标1 , 色标2 , ... )
;

一个完整的进度环

用 conic-gradient 加一个内层遮罩,就能做出纯 CSS 的进度环——不需要 SVG,不需要 canvas。

62%
拖动滑块改变进度 background: conic-gradient(#2563eb 0 62%, #e2e8f0 62% 100%);

实现思路非常直接:外层是一个圆,背景是锥形渐变;内层用 ::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。它们把渐变的色标区间按固定长度无限重复,从而把“一段过渡”变成“一种纹理”。

45° 条纹
棋盘格
波点
多色斜纹
网格线
同心圆

亲手做一个网格背景

“网格线背景”是后台系统、编辑器、画布类产品里最常见的装饰。它的实现只需要两层线性渐变加一个 background-size。

注意这里的关键点:渐变中的线宽如果写成 0,是不会渲染的;必须至少 1px(或者使用小数如 0.5px 在高 DPR 屏上)。同时,为了让线条落在像素网格上,建议让 background-size 是整数,避免出现半像素模糊。

棋盘格的正确写法

/* 用两层 45° 硬边渐变 + 偏移,拼出棋盘格 */
.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 数量不一致,浏览器会循环取值,导致某些层尺寸错乱。写复杂背景时,建议把每一层的注释写清楚,保持缩进对齐。

/* 简写:background 一次性声明多层 */
.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-size 数量要对应层数
位置 可精确控制每一层纹理的偏移
建议 层数控制在 3~6 层,再多难以维护

叠加模式:让层与层真正“融合”

单纯地叠加上去,上层会遮住下层。想要真正融合,需要借助混合模式: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 插值(中间偏灰) */
.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 —— 强制色相递减 */
sRGB(默认)中间偏灰
in oklab感知均匀
in hsl longer hue色彩丰富
in oklch色相环路径

如果你观察上面四条色带,会发现第二条(oklab)明显更“通透”,中间不会出现浑浊的灰绿;第三条(hsl longer hue)则像彩虹一样绕了一大圈,色彩最丰富但也最“吵”。没有绝对正确的选择,只有适合场景的选择。

色带(Banding):另一个常见问题

在大面积、低对比度的渐变上,你可能会看到一圈一圈的“色带”。这不是 bug,而是 8 位色深(每通道 256 级)的物理限制——当颜色的变化小于 1/256 时,浏览器只能就近取整。

解决办法有三种:

  1. 加噪点:在被压缩的色阶上撒一点随机噪点,人眼会把它感知为平滑过渡。
  2. 改用 oklab / oklch 插值:感知均匀的路径往往能减少可见阶梯。
  3. 缩小渐变跨度:避免在极大面积上做极缓慢的颜色变化。
噪点:关闭

上面的噪点层用的是一个内联的 SVG feTurbulence 滤镜,以 data URI 的形式作为背景图,配合 mix-blend-mode: overlay。它的体积只有几百字节,却能让整个渐变背景的质感提升一大截——这是现代 UI 里最划算的一条视觉优化。

渐变的高级用法:文字、边框、遮罩与玻璃

渐变不只能做背景。一旦理解“渐变是一张颜色地图”,你就会发现它可以被用在任何接受图像的地方。

渐变文字 · Gradient Text
渐变边框:外层元素用渐变做背景,内层元素用纯色覆盖,通过 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 组合:用两层遮罩做差集,可以做到任意圆角与不规则形状,是能力最强的方案。
/* 方案三: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 平移。

① 动画 background-position(触发重绘) Paint + Composite
② 超宽渐变 + transform 平移(只走合成) Composite only

在元素数量少、面积小的时候,两者的帧率可能都是 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)

表现为大面积的同心圆或平行条纹。原因在上一节已经解释过,是色深限制。解决手段按性价比排序:

  1. 加一层极低透明度的噪点(成本最低,效果最好)。
  2. 改用 in oklab / in oklch 插值。
  3. 缩短渐变的跨度,或增加中间色标让过渡更“陡”一些。

病二:断层(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. 先定色相家族:主色、辅助色、强调色,各挑 1~2 个色相。
  2. 再定明度梯度:为每个色相准备 3~4 级明度,作为渐变的两端与中间节点。
  3. 统一方向语言:整套产品里,光从哪个方向来?统一成 135° 或 160°,画面会立刻统一。
  4. 统一纹理密度:网格 24px、条纹 12px——把重复图案的节奏定下来,避免每条卡片都不一样。
  5. 抽成 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);
}
必备 三层以内的线性/径向渐变,全平台安全
增强 锥形渐变、混合模式,注意视觉降级
前沿 插值色彩空间、@property,需 @supports
原则 基础可用 + 渐进增强,而不是非此即彼

可访问性:渐变也要讲对比度

渐变背景上的文字,是 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 时,用户不会夸你的渐变好看——他们只会觉得这个页面“很舒服”。那才是最高的评价。