標籤:

CSS3 里,rgba 和 opacity 半透明效果有點不一樣?

代碼段1:

background-color: rgba(34,234,0,0.5);

代碼段2:

background-color: rgb(34,234,0);
opacity: 0.5;

後面為白色,用Chrome打開,RGB分別是:144, 244, 127;145, 245, 128;

不知道什麼原因造成的

10.26補充下,其他瀏覽器樓主還沒來得及試。。


實際上如果背景相同(白色),現在的 Chrome(41) 、Firefox 兩者渲染出來顏色是一樣的,都是 rgb(144, 244, 127)。Demo: JS Bin - Collaborative JavaScript Debugging

WebKit 中有個問題是:
background-color: rgba(34,234,0, 0.5)
最終渲染出來你會發現變成了 background-color: rgba(34,234,0, 0.498039)

一、 故事背景

在與 @顧軼靈 聊到 CSS Alpha 百分比表示方式的時候,偶然發現 Chrome 里他的這個項目 Kolor: color manipulation in sexy syntax. body 的背景色實際計算出來的 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. 轉換為8位無符號整數

A = atof(input) * 255; // atof() 相當於 JS 函數 parseFloat()

=&> A = 0.5 * 255 = 127.5 =&> 127 // 捨去非整數部分

2. 「alphaString」 通過以下定義來計算 Source/platform/graphics/Color.cpp

const char* alphaString = numberToFixedPrecisionString(alpha() / 255.0f, 6, buffer, true);

注意:早期版本的 WebKit 採用 16 位來存儲 Alpha 值,這會導致和現在版本最終計算出來存在差異:http://trac.webkit.org/changeset/126186/trunk/Source/WebCore/css/CSSPrimitiveValue.cpp (參見:http://www.zhihu.com/question/29781299/answer/45617540)

這裡alpha()的結果就是A

alpha() / 255 = 127 / 255 = 0.498039216

然後 (CSS Object Model (CSSOM)) 規範中的 & 值規定最多取 6 位小數,最終反向序列化成字元串,得到你看到的 0.498039。

&
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 規範里演算法是這樣說的:

&
If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:

  1. Let alpha be the given integer.
  2. 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.
  3. 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.
  4. Return the result of serializing rounded as a &.

Otherwise, return the result of serializing the given value as a &.

所以有人提交了 Patch 來修復這個問題,使其反向序列號字元串的時候可以得到同樣的值:
Source/platform/graphics/Color.cpp -

Issue 966903002: Serialize alpha value of a color component to meet the spec. -

Code Review

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

特別鳴謝 @Kyrios Li 對 C++ 代碼的幫助。


今天 @一絲 跟我說我這個項目的 demo渲染出背景色不對,搜了下發現這個問題。
看了爆棧網上各種回復,覺得這應該是一個 WebKit/Blink 的 bug。原因是在目前顏色中的 RGBA 四個通道都是用的 8 位整數,然後返回時把 alpha 通道簡單地除以 255。於是導致如下問題:

document.body.style.backgroundColor = "rgba(255, 255, 255, 0.5)";
console.log(document.body.style.backgroundColor); // rgba(255, 255, 255, 0.498039)

相關 issue:Issue 453414
有人已經提交了一個 patch 修復這個問題:Issue 966903002: Serialize alpha value of a color component to meet the spec. -

Code Review

- const char* alphaString = numberToFixedPrecisionString(alpha() / 255.0f, 6, buffer, true);
+ const char* alphaString = numberToFixedPrecisionString(floatAlpha, 6, buffer, true);

註:根據 @一絲 在評論中的補充,CSS Object Model (CSSOM) 中序列化輸出 & 部分,似乎這也不能算一個 bug。規範允許實現內部使用 8 位整數保存 alpha 值,這樣 0.5 這樣的值讀出來丟失精度也就不奇怪了。只能說這個行為比較令人困惑。


在Firefox和Chrome下都測試過。
Chrome顯示是不正常的,Firefox顯示是正常的。
其中,用Chrome開發者工具查看它的computed style,前者被渲染成 rgba(34, 234, 0, 0.498039) ,alpha值不是0.5。
應該是浮點計算的精度問題。很多軟體都會有這個問題(包括處理數據常用的Excel)。


之前說錯了,兩者確實是有差別,
用rgba的顏色濃度確實要比用rgb+opacity的顏色濃度要高一些,
於是我在chrome下多嘗試了幾次
背景為#000的情況下兩者表現顏色值一致,
背景為#666的情況下兩者表現顏色值一致,
背景為#ddd的情況下兩者表現顏色值不一致,表現同白色背景一致
另外發現用rgba的時候chrome下查看computedStyle發現設置的0.5的alpha值,實際上只有0.49xxx
猜測可能是瀏覽器的bug,不是css標準的問題
由於沒有什麼實質性的回答,所以請求摺疊


rgba的方法是使用第四個alpha值對前三個值分別進行透明計算,然後渲染器再對三個顏色進行合併計算。
而opacity的方法是先渲染整體顏色,然後再進行透明計算。
這就要看不同的瀏覽器在合成渲染時候的演算法實現上的差別了。


背景顏色的rgba只針對背景色,而opacity是針對整個標籤元素的


推薦閱讀:

為什麼RGB色域未能包括人眼能見到的全部色域?
如何識別色彩的明暗?不同顏色的靜物在素描下應該如何表現?
有關於WIN7、專業繪圖級顯示器、PS三者之間的色彩配置問題到底如何是好?
銀色和灰色有什麼區別?能用肉眼區分這兩種發色嗎?
什麼是色彩心理學?

TAG:CSS | 顏色 | CSS3 |