灰阶带源于sRGB线性插值必然经过视觉灰区,需用color-mix(in lch, ...)配合显式中间色阶、lch()起止色、拆分background属性及兜底检测来规避,混入任何sRGB语法即失效。

灰阶带(Gray Dead Zone)不是 CSS 写错了,而是 linear-gradient() 在 sRGB 空间做线性插值时,必然穿过色轮对侧颜色的中间灰区——比如 yellow(rgb(255,255,0))到 blue(rgb(0,0,255)),中点就是 rgb(127.5,127.5,127.5),也就是视觉上“脏”的灰。解决它,得绕开 sRGB 插值缺陷,而不是调饱和度或加阴影。
用 color-mix(in lch, ...) 替代纯 RGB 起止色
Chrome 111+、Safari 16.4+ 支持 color-mix(),指定 in lch 模式后,浏览器会按人眼感知均匀的方式混合颜色,跳过灰区。但不能只写两端,必须显式插入中间色阶:
- 错误写法:
linear-gradient(to right, #ffcc00, #0066cc)—— 仍走 sRGB 插值 - 正确写法:
linear-gradient(to right, #ffcc00, color-mix(in lch, #ffcc00 70%, #0066cc 30%), color-mix(in lch, #ffcc00 30%, #0066cc 70%), #0066cc) - 每个
color-mix()必须独立写在渐变列表里,不能赋值给 CSS 变量再引用 - 别混用
rgba()或background-blend-mode,它们会让整个链路退回到 sRGB
改用 lch() 定义起止色,且禁用 background 简写
lch() 是目前最接近感知均匀的颜色格式,但它的优势只在颜色定义阶段生效;一旦你用 background: linear-gradient(...) 这种简写,浏览器可能把 lch() 值转成 RGB 缓存,失去意义:
- 必须拆开写:
background-color单独设底色,background-image单独放渐变 - 示例:
background-image: linear-gradient(to right, lch(70% 80 90), lch(70% 80 270)); - 过渡动画要动
background-image,而非background—— 否则transition会降级为 RGB 插值 - 兼容性兜底:用
@supports (color: lch(0% 0 0)) { ... }包裹新语法
避免 RGBA 插值导致的意外发灰
很多人想实现“从实色到透明”的渐变,写成 linear-gradient(to right, rgba(255,0,0,1), rgba(255,0,0,0)),结果中间段发白——这是因为浏览器分别插值 R/G/B/A 四个通道,再合成,而 A 下降时 R/G/B 仍在被稀释,叠加在默认白色背景上就显灰:
立即学习“前端免费学习笔记(深入)”;
- 解决方案一:明确设置容器背景色,比如
background-color: #fff,让合成有参照 - 解决方案二:改用带 alpha 的
lch()新语法(Chrome 120+/Safari 17.4+):lch(50% 90 120 / 0.8) - 绝对不要混用
rgba()和lch()在同一个渐变里,哪怕只是其中一个停靠点
低色深屏幕上的 banding 是硬件限制,不是代码问题
多数笔记本 LCD 屏只有 6bit 面板(≈262k 色),而 24bit RGB 理论支持 1677 万色。即使你用 lch() 或 color-mix() 生成了完美过渡,硬件也渲染不出全部中间色:
- 最实用的视觉欺骗方案:叠一层极低透明度噪点 SVG:
background-image: linear-gradient(...), url("data:image/svg+xml,%3Csvg%3E...%3C/svg%3E"); - 启用 GPU 加速能缓解部分渲染失真,检查
chrome://flags/#enable-gpu-rasterization是否开启 - 别指望纯 CSS 在所有设备上消灭 banding,尤其当渐变跨度大、容器高且内容少时,
min-height: 100vh+background简写是防止平铺条纹的基础保障
真正难的不是写出 color-mix(),而是记住:只要掺了任何一点 sRGB 思维(比如混用 rgba、简写 background、忽略背景色),前面所有努力都会失效。LCH 和 color-mix 是工具,不是魔法。


















