上一篇博文中对于现有的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的选择器级别。