渐变刚甩掉「灰廊」,就被 OKLCH 裁出硬边:sRGB / HSL / OKLCH 三条插值路径的实测复盘

发布时间:2026/8/4 1:52:34
渐变刚甩掉「灰廊」,就被 OKLCH 裁出硬边:sRGB / HSL / OKLCH 三条插值路径的实测复盘
在 canvas 或 shader 里做一条红到青的渐变如果直接对 RGB 三个通道做线性插值中间大概率会出现一段脏兮兮的灰棕色。前端圈子里「用 OKLCH」几乎成了标准答案——它确实能让渐变保持鲜艳。但我对 5 组互补色对跑了 100 步实测后发现一个反直觉的结论OKLCH 路径平均有 48.7% 的插值步会越出 sRGB 色域直接截断就会产生硬边。灰廊是被赶走了硬边又住进来了。这篇文章不站队。我只把 sRGB、HSL、OKLCH 三条插值路径放在同一把尺子上量一量看它们分别在「灰廊深度」「色相绕路」「色域裁剪」三个维度上表现如何。背景做渐变时我们到底在插什么颜色插值不是把两个十六进制字符串平均一下那么简单。你选的色空间本质上是在给中间色规定一条路径。sRGB把两个颜色当成 RGB 三维空间里的点直接拉直线。这条线很容易穿过色立方中心附近于是红到青的中点会冲到(128,128,128)这种灰褐色。HSL先把端点转到色相-饱和度-亮度圆柱再分别插值。它绕开了灰廊但色相是按角度走的最短弧经常要经过一个你根本没选的第三色。OKLCH在感知均匀的 OKLab 柱坐标里插值。它保证路径最短、亮度均匀代价是这条感知直线经常穿出 sRGB 这个小色域。大多数开发者对 sRGB 的灰廊有痛感对 HSL 的色相绕路不太敏感对 OKLCH 的色域裁剪几乎没概念。下面我把三种路径都实现了一遍用同一组数据说话。三种插值路径的实现与观察我挑了生成艺术和 UI 里最常见的 5 组互补/近互补色对红→青、蓝→橙、品红→绿、紫→黄、粉→青蓝。每种色对分别用 sRGB、HSL、OKLCH 插 100 步再统一转回 sRGB 显示。图1红→青在三种插值空间下的路径对比。sRGB 中点发灰HSL 绕路穿过品红OKLCH 走最短感知路径并保鲜艳。只看图 1 就能读出三个关键现象sRGB 红→青的中点明显发灰HSL 虽然鲜艳但路径从红拐到品红再到青蓝完全不是「最短路径」OKLCH 看起来最自然红与青之间的过渡饱满均匀。但这只是肉眼。我们要把「灰廊深度」「色相绕路」「色域裁剪」量化出来。实证一最小感知色度 C灰廊深度我把每条路径上的中间颜色再统一转到 OKLCH读取其色度C。C越低说明路径上最灰的那一步越接近灰廊。结果如下色对sRGB 最小 CHSL 最小 COKLCH 最小 C红→青0.0440.1680.133蓝→橙0.0230.2150.156品红→绿0.0610.1520.189紫→黄0.0880.1700.125粉→青蓝0.0680.1740.122平均最小 CsRGB ≈ 0.057HSL ≈ 0.176OKLCH ≈ 0.145。图2五条色对的最小感知色度 C 对比。sRGB红几乎贴着 0HSL黄与 OKLCH绿都保持在 0.12 以上。sRGB 在互补色渐变里几乎必然制造灰廊这是 RGB 立方几何决定的。HSL 和 OKLCH 都能把最小 C 拉到 0.12 以上视觉上不再发灰。实证二色相绕路度数HSL 的谎言HSL 保鲜艳的代价是色相绕路。我把每条路径上每一步的 OKLCH 色相累加得到路径实际穿越的色相总跨度色对sRGB 色相跨度HSL 色相跨度OKLCH 色相跨度红→青154.8°134.8°0.1°蓝→橙188.3°135.8°3.0°品红→绿129.6°115.6°1.0°紫→黄142.4°133.3°13.0°粉→青蓝145.2°137.8°0.1°HSL 平均绕路约 131°OKLCH 平均不到 4°。这意味着用 HSL 做「红→青」渐变浏览器会替你悄悄经过品红做「蓝→橙」渐变会经过紫和红。如果你的品牌或数据语义要求严格控制过渡色HSL 这个自动绕路就是 bug而不是特性。实证三OKLCH 的越界裁剪率OKLCH 的麻烦不在灰廊而在色域。它描述的是一个比 sRGB 大得多的感知空间一条感知直线从 sRGB 内的一点走到另一点很容易穿出 sRGB 的边界。我把每条 OKLCH 路径上越出[0,1]的 RGB 采样点比例算了出来色对OKLCH 越界比例红→青0%蓝→橙35%品红→绿45%紫→黄66%粉→青蓝0%平均越界比例 48.7%。图3OKLCH 路径越出 sRGB 色域的比例。紫→黄高达 66%不做 gamut 映射直接截断会出现明显硬边。越界本身不是错误但你如果在 canvas 或 shader 里 naive 地把r/g/b截断到[0,1]那些越界采样点会被拍扁到色域边界上形成肉眼可见的硬边。CSSlinear-gradient(in oklch, ...)浏览器会自动做 gamut 映射所以网页渐变里不明显可一旦你手写 OKLCH 插值并自己转回 RGB这就是必须处理的坑。局限与落地建议这套实测只覆盖 sRGB 端点没碰 P3、Rec.2020 等广色域也没测试不同 gamut 映射算法clip、chroma 缩减、L 保持对视觉的影响。但它足够说明选色空间不是「哪个更好」而是「你愿意为哪种副作用买单」。落地建议网页 CSS 渐变直接用linear-gradient(in oklch, ...)浏览器会帮你处理色域映射风险最低。canvas / WebGL / 自写算法如果手动做 OKLCH 插值务必加 gamut 映射比如迭代降低 chroma 直到 RGB 回退到[0,1]而不是简单截断。色相相近的端点比如蓝→紫HSL 绕路不严重计算又比 OKLCH 轻完全可以继续用。互补/近互补端点避开 sRGB 线性插值如果性能敏感且不需要精确感知路径HSL 是折中方案。结论与下一步三种插值路径都守恒但守恒的不是同一个东西sRGB 守恒的是 RGB 三个通道的算术平均代价是灰廊HSL 守恒的是色相弧长与饱和度代价是绕路OKLCH 守恒的是感知距离代价是色域裁剪。没有银弹。如果你在写生成艺术、数据可视化或设计系统值得把这条判断写进项目规范互补色渐变用 OKLCH gamut 映射邻近色用 HSLsRGB 插值只作为兜底 fallback。开源地址结论段指向同一组织即可3 个矩阵门户https://github.com/wangzifan396-wzf/WB单文件工具聚合器https://github.com/wangzifan396-wzf/nano-workbenchGitHub 组织主页wangzifan396-wzf (WangZi) · GitHub