jincdream / jincdream.github.io

blog
14 stars 1 forks source link

真正理解"CSS选择器的优先级" #14

Open jincdream opened 6 years ago

jincdream commented 6 years ago

img_0236

题图、摄影:锦此

当我们在讨论CSS选择器优先级的时候,我们再讨论什么?

其实很多人都对此有点模糊,那我换个方式问: 一个CSS属性的最终值是怎么来?

回答 : CSS属性的最终值是通过层叠计算得来的。

那什么是层叠计算呢?

我通俗的理解,其实就是先计算重叠

层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称层叠样式表正是强调了这一点。

计算过程

计算的过程指的是用户代理(浏览器只是用户代理的一种“实例”)在渲染HTML的时候,对CSS进行层叠计算的过程(这里不讨论浏览器的渲染、重绘等触发时机)。

为了方便理解,这里只针对一个属性值(padding)进行讨论,其他的属性值都是一样的过程。


demo:

<div class="taobao_com" id="taobao_com" data-show="true">
    <div class="taobao"></div>
    <p>taobao.com</p>
</div>
div{
  padding:1px;
}
.taobao_com{
  padding:12px;
}
div .taobao{
  padding:123px;
}
.taobao_com .taobao{
  padding:1234px;
}

在属性的计算之前,会对每个文档元素的每个属性上的值进行排序 (W3C文档地址):

1. Transition declarations [CSS3-TRANSITIONS]
2. Important user agent declarations
3. Important user declarations
4. Important override declarations [DOM-LEVEL-2-STYLE]
5. Important author declarations
6. Animation declarations [CSS3-ANIMATIONS]
7. Normal override declarations [DOM-LEVEL-2-STYLE]
8. Normal author declarations
9. Normal user declarations
10.Normal user agent declarations

排序靠前的会比靠后的优先级要高。 1 > 2 > 3 > 4 > 5 ...

假设我们的用户代理是Chrome浏览器,那么当我们要计算A:<div class="taobao"></div>padding值的时候,会有这么一个排序过程(简化):

1.  浏览器中对该标签的``padding``默认值
2.  开发者对该标签的 ``padding`` 进行声明的值
3.  浏览器中对该标签的 ``padding`` 进行 **!important** 声明的值
4.  开发者对该标签的 ``padding`` 进行 **!important** 声明的值

那么在demo里面,分别写了4条padding的的声明,但都会被排在层叠顺序开发者对该标签的 padding 进行声明的值之中,而且padding没有更高权重的声明了,那么就会对这个声明队列里的声明进行优先级的计算。


所以,!important 跟选择器优先级是什么关系?

优先级的计算

优先级的计算首先是 选择器权重 的优先级计算,然后是 声明先后顺序 的优先级计算。

选择器优先级的权重计算

这时候,才到了选择器优先级登场。

选择器的权重并不是网上很多人说的

选择器通过权重值 1(div等)、10(class)、100(id)

这样的方式进行的,这只是别人理解出来的东西,真正的计算原理可以翻阅W3C文档选择器权重的计算

文档指出:

A selector’s specificity is calculated for a given element as follows:

1.count the number of ID selectors in the selector (= A) 2.count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B) 3.count the number of type selectors and pseudo-elements in the selector (= C) 4.ignore the universal selector

文档也给了例子:

Examples:
*               /* a=0 b=0 c=0 */
LI              /* a=0 b=0 c=1 */
UL LI           /* a=0 b=0 c=2 */
UL OL+LI        /* a=0 b=0 c=3 */
H1 + *[REL=up]  /* a=0 b=1 c=1 */
UL OL LI.red    /* a=0 b=1 c=3 */
LI.red.level    /* a=0 b=2 c=1 */
#x34y           /* a=1 b=0 c=0 */
#s12:not(FOO)   /* a=1 b=0 c=1 */
.foo :matches(.bar, #baz)
                /* Either a=1 b=1 c=0
                   or a=0 b=2 c=0, depending
                   on the element being matched. */

大致意思就是,把权重分为了 A,B,C 三个级别,A > B > C , A,B,C 直接各自计算。也就是会优先计算 A 的权重,如果相等会计算 B 的权重,以此类推。

所以才有了100、10、1的说法,但很不准确。

结合之前的demo,我们来计算下权重值:

1:

div { /*type selectors*/
  padding:1px;   /*a = 0 , b = 0 , c = 1*/
}

2:

.taobao_com{  /*class selectors*/
  padding:12px;  /*a = 0 , b = 1 , c = 0*/
}

3:

div .taobao{ /*type selectors + class selectors*/
  padding:123px;  /*a = 0 , b = 1 , c = 1*/
}

4:

.taobao_com .taobao{ /* class selectors + class selectors */
  padding:1234px;   /*a = 0 , b = 2 , c = 0*/
}

由于A位相等,B位差异的最大值在 4 ,得到的结果将会是 第4条声明 胜出:

  1. a = 0 , b = 0 , c = 1
  2. a = 0 , b = 1 , c = 0
  3. a = 0 , b = 1 , c = 1
  4. a = 0 , b = 2 , c = 0

声明先后顺序

当 A 、B 、C 所计算的权重都相等时(ABC三个值相等)相等时,后面声明的值将会是最终的计算值。

最终得到了CSS属性的值。

jincdream commented 6 years ago

值得注意的是,MDN这个关于层叠的文档的中文版是错的。

另外,(MDN文档)写到:

同时仍应注意用@keyframes @规则定义的值会覆盖全部普通值,但会被!important的值覆盖。

W3C文档也有指出:

  1. Important user declarations
  2. Animation declarations [CSS3-ANIMATIONS]

但在Chrome浏览器(65.0.3325.162(正式版本) (64 位))测试的时候,

.img {
    animation: doudong 1.5s linear infinite alternate;
    transform: rotate(100deg) !important;
}

transfrom 的 !important 并没有覆盖 animation 的。

https://codepen.io/jincdream/pen/xWPQZM

难道我理解错了?

jincdream commented 6 years ago

经过查阅与测试,对规范的理解并没有错,上面的codepen在Firefox 58.0.2 (64 位) 上表现才是符合标准的。

@keyframes 的声明会是一整块参与层叠计算。 看来 chrome 又一次违背了标准