關於css中選擇器優先順序的總結
上一篇博文中對於現有的css的選擇器進行了總結。接下來對於css中的優先順序進行一下總結,總結可能不盡精確,如有不妥之處,還望前輩即時指出。
css選擇器優先順序
開發中可能會遇到這樣的問題,在兩個css選擇器都能定位到某元素,但是瀏覽器按照哪個選擇器定義的樣式來渲染元素呢。這就是涉及到css選擇器優先順序的問題。
css2.1的規範是這樣描述的:
1.如果宣告來自“style”屬性,而不是帶有選擇器的規則,則記為1,都則記為0(=a),這種樣式被稱之為內聯樣式,因為這些樣式沒有選擇器,因為記為a=1,b=0,c=0,d=0
2.選擇器中ID選擇器的個數(=b)
3.選擇器中類原則器的個數、屬性選擇器的個數以及偽類選擇器的個數(=c)
4.選擇器中標籤選擇器的個數以及偽元素的個數(=d)
以上四個數字按照a-b-c-d組合起來,便構成了選擇器的優先順序。
在最新的Selectors Level3 規範中是這樣描述的:
1.計算選擇器中ID選擇器的個數(=a)
2.計算選擇器中類選擇器的個數、屬性選擇器以及偽類選擇器的個數(=c)
3.計算選擇器中標籤選擇器的個數以及偽元素的個數(=d)
4.忽略通用選擇器*
將以上三個數字按照a-b-c組合起來,構成選擇器的優先順序。
如何解讀css優先順序
上述篇幅描述了規範中是如何對css優先順序進行的定義。對於如何解讀css的優先順序網上給出了不同的方法。有的使用a*1000+b*100+c*10+d。這種方式值得商榷。切忌不要使用這種方式進行計算,雖然這樣計算可能在目前我們所遇到的需要計算選擇器優先順序的場景中不會出錯。
瀏覽器引擎是如何計算css優先順序的
- webkit優先順序計算程式碼
unsigned CSSSelector::specificity() const
{
// make sure the result doesn't overflow
static const unsigned maxValueMask = 0xffffff; // 整個選擇器的最大值,十進位制表示:idMask + classMask + elementMak = 16777215
static const unsigned idMask = 0xff0000; // ID選擇器的最大值,十進位制表示:(16*16+16)*16^4=16711680
static const unsigned classMask = 0xff00; // class(偽類、類)選擇器的最大值,十進位制表示:(16*16+16)*16^2=65280
static const unsigned elementMask = 0xff; // 元素選擇器的最大值,十進位制表示:16*16+16=255
if (isForPage())
return specificityForPage() & maxValueMask;
unsigned total = 0;
unsigned temp = 0;
for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) {
temp = total + selector->specificityForOneSelector();
// Clamp each component to its max in the case of overflow.
if ((temp & idMask) < (total & idMask)) // 判斷是否為ID選擇器
total |= idMask; // 保證ID選擇器的同類疊加不會超過ID選擇器的總最大值,下同
else if ((temp & classMask) < (total & classMask))
total |= classMask;
else if ((temp & elementMask) < (total & elementMask))
total |= elementMask;
else
total = temp;
}
return total;
}
inline unsigned CSSSelector::specificityForOneSelector() const
{
// FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function
// isn't quite correct.
switch (m_match) {
case Id:
return 0x10000; // ID選擇器權重
case PseudoClass:
// FIXME: PsuedoAny should base the specificity on the sub-selectors.
// See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
if (pseudoClassType() == PseudoClassNot && selectorList())
return selectorList()->first()->specificityForOneSelector();
FALLTHROUGH;
case Exact:
case Class:
case Set:
case List:
case Hyphen:
case PseudoElement:
case Contain:
case Begin:
case End:
return 0x100; // class選擇器權重
case Tag:
return (tagQName().localName() != starAtom) ? 1 : 0; // 元素選擇器權重
case Unknown:
return 0;
}
ASSERT_NOT_REACHED();
return 0;
}
webkit中對於a級別(內聯樣式)是不參與計算的。對於b級(ID選擇器)、c級(class選擇器)、d級(元素選擇器),每一級都有自己的最大值(最大數目為255),其中b級(ID選擇器)的權重為65536,c級(類選擇器)的權重為256,d級(標籤選擇器)的權重為1。如果某一級別中的陣列超過其最大值255,則使用其最大值255,因此不會出現低一級別超出一定數目後導致高一級被覆蓋的情況。
webkit中對於!important的處理是這樣的,具有!important的樣式規則優先順序大於!important的規則,只有在同時具有!important的時候才會比較css整體優先順序。
因此得出webkit中的優先級別:
!important>inline>ID>class>tag
- mozilla中優先順序計算程式碼
int32_t nsCSSSelector::CalcWeightWithoutNegations() const
{
int32_t weight = 0;
#ifdef MOZ_XUL
MOZ_ASSERT(!(IsPseudoElement() &&
PseudoType() != nsCSSPseudoElements::ePseudo_XULTree &&
mClassList),
"If non-XUL-tree pseudo-elements can have class selectors "
"after them, specificity calculation must be updated");
#else
MOZ_ASSERT(!(IsPseudoElement() && mClassList),
"If pseudo-elements can have class selectors "
"after them, specificity calculation must be updated");
#endif
MOZ_ASSERT(!(IsPseudoElement() && (mIDList || mAttrList)),
"If pseudo-elements can have id or attribute selectors "
"after them, specificity calculation must be updated");
if (nullptr != mCasedTag) {
weight += 0x000001;
}
nsAtomList* list = mIDList;
while (nullptr != list) {
weight += 0x010000;
list = list->mNext;
}
list = mClassList;
#ifdef MOZ_XUL
// XUL tree pseudo-elements abuse mClassList to store some private
// data; ignore that.
if (PseudoType() == nsCSSPseudoElements::ePseudo_XULTree) {
list = nullptr;
}
#endif
while (nullptr != list) {
weight += 0x000100;
list = list->mNext;
}
// FIXME (bug 561154): This is incorrect for :-moz-any(), which isn't
// really a pseudo-class. In order to handle :-moz-any() correctly,
// we need to compute specificity after we match, based on which
// option we matched with (and thus also need to try the
// highest-specificity options first).
nsPseudoClassList *plist = mPseudoClassList;
while (nullptr != plist) {
weight += 0x000100;
plist = plist->mNext;
}
nsAttrSelector* attr = mAttrList;
while (nullptr != attr) {
weight += 0x000100;
attr = attr->mNext;
}
return weight;
}
int32_t nsCSSSelector::CalcWeight() const
{
// Loop over this selector and all its negations.
int32_t weight = 0;
for (const nsCSSSelector *n = this; n; n = n->mNegations) {
weight += n->CalcWeightWithoutNegations();
}
return weight;
}
在mozilla中唯一與webkit中存在差別的就是沒有對同一類別的選擇器進行最大值控制,僅僅是結果的直接相加,這樣的話會導致同一級別選擇器數目多餘255後高一級加1,出現結果溢位的情況。這樣就會出現如果定義256個class選擇器幹掉一個id選擇器的情況。這點張鑫旭大大的文章 有趣:256個class選擇器可以幹掉1個id選擇器也解釋過這種現象。
2.優先順序計算總結
- 優先順序計算可能會存在溢位問題
- 優先順序計算不包括內聯樣式以及!important的情況
- 優先順序計算中只有同一類別才具有可比性
3.為什麼如果按照十進位制的權重進行相加求和計算優先順序的過程一般不會出錯
如果採用這種計算方法的話,如果同一級別的選擇器數目在10個以內是不會出現問題的(一般的css選擇器語句不會超過10個),一旦選擇器數目大於10個的話這樣的計算方式計算出來的優先順序就會出現問題。例如:
A的選擇器語句對應的級別:a:0 b:1 c:0 d:0
B的選擇器語句對應的級別:a:0 b:0 c:11 d:0
如果按照上面的計算方式計算出來的權重分別為:A:100,B:110,這樣的話A的權重就會小於B的權重。事實上因為A中存在一個ID選擇器,B中僅存在11個類選擇器,所以A的優先級別是大於B的優先順序級別的。很顯然單純的使用十進位制權重對css級別進行解讀是行不通的,切忌不要這樣使用。
4.正確的解讀css優先順序
css優先順序的比較僅限於同級別的選擇器進行比較。比如:
A選擇器語句對應的級別為: a:0 b:1 c:1 d:2
B選擇器語句對應的級別為: a:0 b:0 c:2 d:3
以上兩個選擇器語句的級別在比較的時候發現A中ID選擇器級別(對應的是b)是1,而B中ID選擇器級別對應的是0.這個時候應該就結束比較了。可以確定A的選擇器優先順序大於B的選擇器級別。