浏览器对 background-color 或 color 的过渡默认在 srgb 空间线性插值,必然经过灰紫等低饱和中间色,因 rgb 通道独立线性运算且 srgb 非感知均匀空间;oklab/lch 插值需通过 @keyframes、color-mix 或 js 手动实现,纯 css 中写 oklab() 不触发对应插值。

浏览器对 background-color 或 color 的过渡,默认在 sRGB 空间做线性插值,不是 bug,是数学必然——它会让红蓝之间必经灰紫,不是“没调好”,而是 RGB 通道各自拉直线的结果。
RGB 插值为什么必然经过灰色地带
sRGB 插值本质是把起始色和结束色的 R、G、B 三个分量分别做线性运算。例如从 rgb(0, 0, 255)(纯蓝)到 rgb(255, 0, 0)(纯红),中间点就是 rgb(128, 0, 128)(紫灰),再往中心走,G 始终为 0,R 和 B 对称增长/衰减,最终逼近 rgb(128, 0, 128) → rgb(128, 0, 128) 这类低饱和、中明度的“灰调”。人眼对色相和饱和度变化敏感,但 sRGB 不是感知均匀空间,所以视觉上就“浑浊”了。
常见错误现象:
- DevTools 里看到颜色值在变,但肉眼只看到“蓝→紫灰→红”,中间一段像蒙了层雾
- 同一段 transition 在 Chrome 和 Safari 上灰带位置不同(因底层 sRGB gamma 处理略有差异)
- 用
hsl(240, 100%, 50%)→hsl(0, 100%, 50%)仍发灰——浏览器根本没按 HSL 插值,只是把 HSL 转成 RGB 后再算
OKLAB 插值真能避免浑浊吗?
能,但仅限你真正控制插值路径时。OKLAB 是感知均匀空间:等距移动 ≈ 等量视觉变化。它把亮度(L)、红绿轴(a)、黄蓝轴(b)解耦,插值时不会因通道耦合强行压低饱和度。
但关键点是:纯 CSS 中写 oklab() 并不会触发 OKLAB 插值。浏览器仍会把它转成 RGB,再走老路。目前唯一能启用 OKLAB 插值的原生场景只有:
-
@keyframes动画中,所有关键帧统一用oklab()或lch()(Chrome 112+、Safari 16.4+) -
color-mix(in oklab, ...)静态混合(Chrome 111+),它不依赖运行时插值,结果可预测 - JS 调用
colorjs.io或culori库生成 OKLAB 路径上的离散 RGB 点,再喂给linear-gradient或自定义属性
别信“只要写 oklab 就自动变丝滑”——那是把输入语法糖当执行引擎了。
怎么让 transition 看起来不浑浊(实操建议)
绕过底层限制,靠路径设计 + 节奏控制骗过眼睛:
- 用
color-mix(in oklch, ...)手动插入 2–3 个中间色,比如从oklch(0.4 0.25 240)到oklch(0.7 0.22 270),先加一个color-mix(in oklch, ... 50%)作为缓冲色 - 改用
steps(6, end)替代ease:把过渡切成 6 段跳变,人眼反而觉得“节奏清晰”,比一段浑浊渐变更干净 - 起止色的 OKLCH 参数要约束:L 差 ≤ 0.3,H 差 ≤ 90°(如
200deg → 270deg安全;10deg → 300deg危险),否则 OKLCH 自身也会因 a/b 接近零导致 H 抖动,视觉上还是灰 - 禁用
transition: all,显式写transition: background-color 0.35s cubic-bezier(0.4, 0.1, 0.3, 1),这个贝塞尔曲线比默认ease更适合明度主导的变化
最容易被忽略的细节
OKLCH 的 H 是角度,但浏览器不做跨 0°/360° 归约;你写 oklch(0.6 0.25 350) → oklch(0.6 0.25 10),它真按 340° 差值算,而不是 -20°,结果中间段色度被强制压低——这不是 OKLCH 的问题,是你没手动拆段。另外,Safari 对 oklch() 的 alpha 支持不稳定,涉及透明度时优先用 rgba() + JS 控制,别赌渲染一致性。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











