CSS渐变和混合视觉“偏暗”是因为浏览器默认在非线性sRGB空间计算,而人眼感知和物理光照是线性的;需用标准分段公式(≤0.04045时除12.92,否则pow((x+0.055)/1.055,2.4))转线性空间运算,color-mix(in lch/oklab)或Canvas/WebGL端到端处理才可控。

为什么CSS渐变和混合在视觉上“偏暗”
因为浏览器默认在sRGB(非线性)空间做颜色计算,而人眼感知和物理光照都是线性的。比如 linear-gradient(to right, #808080, #ffffff),中间色本该是视觉中灰,但直接插值得到的 #bfbfbf(sRGB值 0.75)在线性空间实际亮度只有约 0.42,看起来明显偏暗——这不是bug,是Gamma没对齐的结果。
sRGB转线性RGB的两个关键公式要记牢
所有涉及颜色混合、插值、遮罩或滤镜的前端逻辑,都该先转成线性空间再算。别用粗略的 pow(x, 2.2),sRGB有分段定义:
- 输入值 ≤ 0.04045:用
x / 12.92 - 输入值 > 0.04045:用
Math.pow((x + 0.055) / 1.055, 2.4)
反向转换(线性→sRGB)也需对应分段,否则在暗部会出现阶跃误差。注意:2.4 是 sRGB 标准指数,不是近似值 2.2;后者只适用于旧式 Gamma 空间或快速估算。
CSS本身不提供线性空间运算,得自己兜底
目前 color-mix()、color() 函数虽支持 color(srgb ...) 和 color(display-p3 ...),但底层仍以 sRGB 值参与插值——除非显式指定 in lch 或 in oklab,否则无法绕过非线性陷阱。
立即学习“前端免费学习笔记(深入)”;
- 动画中用
@keyframes插值颜色?必须用 JS 提前算好线性中间值,再转回 sRGB 输出 - Canvas 2D 绘图时调用
ctx.fillStyle = 'rgb(...)')?它直接走 sRGB 渲染管线,混合操作无效,得用putImageData()手动写线性像素 - WebGL/Three.js 场景里用
THREE.Color?默认当 sRGB 输入,记得设material.color.convertSRGBToLinear()
最容易被忽略的坑:图片与CSS颜色混用时的隐式Gamma冲突
一张 background-image: url(photo.jpg) 默认是 sRGB 编码,而你写的 background-color: #ff6b6b 也是 sRGB 值,看似统一——但问题出在叠加方式:CSS 的 background-blend-mode 在大多数浏览器中仍基于 sRGB 值直接混合,而非解码后在线性空间计算。结果就是阴影发灰、高光溢出、半透明叠加强度偏低。
真正可控的解法只有两个:要么整页启用 color-gamut: p3 + color-scheme: light dark 并配合 color(display-p3 ...) 显式声明色彩空间;要么放弃纯 CSS 方案,改用 Canvas 或 WebGL 做端到端线性处理。别指望 image-rendering: -webkit-optimize-contrast 这类老属性能救场——它跟Gamma无关,只是控制缩放算法。


















