css颜色动画在srgb空间线性插值会导致视觉偏暗发灰,因srgb非线性而人眼感知亮度不均;改用lch()或color-mix(in lch)可实现感知均匀过渡,但需chrome 112+/safari 16.4+支持并全程使用对应格式。

因为 rgb()、hsl()、#RRGGBB 这些 CSS 颜色表示法默认在 sRGB 色彩空间中解释,而 sRGB 是非线性的——它的数值和人眼感知的亮度不成正比。直接用它们做插值、渐变或对比度计算,结果会失真。
rgb() 插值不是视觉均匀的,动画容易发灰
sRGB 通道值经过伽马压缩(γ ≈ 2.2),rgb(100, 0, 0) 的实际相对亮度约是 rgb(200, 0, 0) 的 22%,不是 50%。CSS 动画对 rgb() 做线性插值时,浏览器按数值等距过渡,但人眼看到的是明度跳变+中间段发灰。
- 典型现象:红色 → 黄色的
@keyframes动画,中间出现脏棕色或灰褐色,而不是干净的橙色过渡 - 根本原因:插值路径在 sRGB 立方体对角线上,而人眼对明度变化最敏感,该路径上 L*(CIELAB 明度)不匀速变化
- 替代方案:用
lch()或lab()写渐变,例如background: linear-gradient(lch(50% 80 40), lch(70% 60 60)),L 和 C 分量可独立、感知均匀地控制
hsl() 的 l 参数不能代替 WCAG 对比度计算
hsl(0, 100%, 50%) 和 hsl(0, 100%, 60%) 的 l 差 10%,但实际相对亮度差约 18%(经 sRGB → 线性 RGB → luminance 转换后),且不同色相下同一 l 值对应的真实亮度差异极大。
- 错误做法:用
l值估算文本与背景对比度,比如认为hsl(0, 0%, 90%)和hsl(0, 0%, 10%)对比度≈8:1 - 正确做法:WCAG 2.1/2.2 要求的对比度必须基于线性化 sRGB 值计算相对亮度(
Y = 0.2126×R + 0.7152×G + 0.0722×B),l完全不可替代 - 工具建议:用 Chrome DevTools 的“Accessibility”面板查真实对比度,或用
getComputedStyle(el).color+ 库(如colorjs.io)解析出线性值再算
hex 和 #RRGGBBAA 的透明度混合依赖线性空间
当使用 #FF000080(半透红)叠加在白色背景上时,浏览器按 sRGB 数值做 alpha 混合:result = foreground × alpha + background × (1 − alpha)。但这个公式只在线性光空间下物理正确;sRGB 下直接套用会导致颜色过饱和或暗部细节丢失。
- 常见问题:深色模式下,
#00000080叠加在#121212上,视觉灰度比预期更浅,因为两个非线性值相加放大了伽马误差 - 规避方式:避免用半透色直接覆盖复杂背景;改用
backdrop-filter: brightness(0.8)或预合成图层 - 高阶控制:CSS
color-mix()支持指定色彩空间,如color-mix(in lch, red 50%, white 50%),能绕过 sRGB 插值缺陷
真正关键的不是“要不要线性”,而是“在哪一步必须线性”:颜色定义本身可以继续用 rgb()(兼容性好),但任何涉及插值、混合、对比度判断、无障碍评估的操作,都必须先转到线性空间或感知均匀空间(如 lch())处理。漏掉这步转换,就是拿数学近似当物理事实用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











