blog
blog copied to clipboard
有趣的 CSS Alpha 值
在 Chrome 中输入 「background-color: rgba(0, 0, 0, 0.5)」
最终渲染出来你会发现变成了 「background-color: rgba(0, 0, 0, 0.498039)」。
阿西!这是什么鬼?( ・᷄ὢ・᷅ ) ,别急下面慢慢揭开它的神秘面纱。
一、 故事背景
在与 @Justineo 聊到 CSS Alpha 百分比表示方式的时候,偶然发现 Chrome 里他的这个项目kolor 的背景色实际计算出来的 Alpha 值和指定的不一样。
这引起了我们的兴趣,一番探讨搜索后,做个简要总结,备忘。
二、概念
CSS Color 中阿尔法通道(α Channel 或 Alpha Channel)是指颜色的不透明度,而不是透明度。也就是说:
- 不透明度=0 的时候是全透明。
- 不透明度=1 的时候就是完全不透明。
其中 32 位的颜色被分解成四个8位整数:
- R: 8位
- G: 8位
- B: 8位
- A: 8位
所以 Alpha 值在内部表示为介于0和255之间的一个整数(即8位无符号整数)。为了更加方便开发者使用,书写的时候我们采用 0-1 之间的浮点数来表示 Alpha 值。
三、 探索
那么我们看看 Chrome 中是如何计算出 0.5 的 Alpha 值的呢?
我们用input
来表示输入 CSS 值中解析出来的字符串,A
代表最终的 Alpha 值,alphaString
来表示 CSS Color 中 Alpha 值的序列化结果:
1 把 0.5 转换为8位无符号整数
A = atof(input) * 255; // atof() 相当于 JS 函数 parseFloat()
=> A = 0.5 * 255 = 127.5 => 127 // 舍去非整数部分
2 alphaString
通过以下定义来计算
const char* alphaString = numberToFixedPrecisionString(alpha() / 255.0f, 6, buffer, true);
注意:早期版本的 WebKit 采用 16 位来存储 Alpha 值,这会导致和现在版本最终计算出来存在差异,参见:http://www.zhihu.com/question/29781299/answer/45617540
这里alpha()的结果就是 A
alpha() / 255 = 127 / 255 = 0.498039216
然后 CSS Object Model (CSSOM) 规范中的 <number>
值规定最多取 6 位小数,最终反向序列化成字符串,得到你看到的 0.498039。
<number>
A base-ten number using digits 0-9 (U+0030 to U+0039) in the shortest form possible, using "." to separate decimals (if any), rounding the value if necessary to not produce more than 6 decimals, preceded by "-" (U+002D) if it is negative.
这里 WebKit 偷懒了啊:
CSSOM 规范里的算法是这样说的:
<alphavalue>
If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:
- Let alpha be the given integer.
- If there exists an integer between 0 and 100 inclusive that, when multiplied with 2.55 and rounded to the closest integer (rounding up if two values are equally close), equalsalpha, let rounded be that integer divided by 100.
- Otherwise, let rounded be alpha divided by 0.255 and rounded to the closest integer (rounding up if two values are equally close), divided by 1000.
- Return the result of serializing rounded as a
<number>
. Otherwise, return the result of serializing the given value as a<number>
.
所以有人提交了 Patch 来修复这个问题,使其反向序列号字符串的时候可以得到同样的值:
if (colorHasAlpha) {
result.appendLiteral(", ");
double floatAlpha = 0;
// See section in CSS Object Model (CSSOM)
// Why ceil, not round here is that we ommit decimals in CSSPropertyParser::parseColorParameters.
for (int i = static_cast < int > (ceil(alpha() / 2.56)); i <= 100; i) {
int computedIntAlpha = static_cast < int > (i * nextafter(2.56, 0.0));
if (computedIntAlpha == alpha()) {
floatAlpha = i * 0.01;
break;
}
if (computedIntAlpha > alpha())
break;
}
if (!floatAlpha)
floatAlpha = ceil(alpha() / nextafter(0.256, 0.0)) * 0.001;
NumberToStringBuffer buffer;
const char * alphaString = numberToFixedPrecisionString(alpha() / 255.0 f, 6, buffer, true);
//const char * alphaString = numberToFixedPrecisionString(floatAlpha, 6, buffer, true);
result.append(alphaString, strlen(alphaString));
}
更多
- Firefox 比较忠实的还原了用户的输入,这里是相关代码:http://hg.mozilla.org/mozilla-central/file/9b6b80222e66/layout/style/nsStyleUtil.cpp#l567
- 更多讨论参阅:https://lists.w3.org/Archives/Public/www-style/2015Jan/0596.html
特别鸣谢 @kyriosli 对 C++ 代码的帮助٩(๑ᵒ̴̶̷͈᷄ᗨᵒ̴̶̷͈᷅)و 。
更多讨论请移步知乎:http://www.zhihu.com/question/26248345/answer/45773454
٩(๑ᵒ̴̶̷͈᷄ᗨᵒ̴̶̷͈᷅)و
好高深,膜拜一下
NB
:+1:
研究问题就是要这么深刻才行~
还可以研究得这么深入,学习一下!
还是姐姐看到远啊
一丝姐姐人气好高
ysjj人气好高
真够深入的~~
牛逼带闪电,佩服!