你好Even

CSS属性计算过程

2024-05-16
CSS
本文介绍了在CSS中属性的计算过程, 其中涉及到CSS 的一些最基本的概念——层叠、优先级和继承——这些概念决定着如何将 CSS 应用到 HTML 中,以及如何解决冲突。
你好,我是Even。


请问你是否了解过CSS中属性的计算过程呢?


说到CSS的属性,你可能会说,这个我了解,例如:


h1 {
  color: red;
}


上面CSS代码中,h1是元素选择器,代码块中的color是一个CSS属性,red是属性值。


但是你说它的计算过程,不太清楚,难道不是声明的属性值是什么就是什么吗?


假设有个html文件如下


<body>  
<h1>你好Even</h1>
</body>


在浏览器中打开,你会看到下图


p1


你是否会好奇,明明你没有为它添加过任何样式,但这个h1元素目前是有默认样式的,比如字体大小、粗体、颜色等等。那么问题来了,这个h1元素究竟还有哪些属性呢?


浏览器中按F12打开的开发者工具可以为我们解答,通过点击元素 => 点击H1元素 => 计算样式 => 全部显示,这里能够看到该元素的CSS属性计算结果。可以发现该元素上有CSS所有的属性。


p2



正如开发者工具中所示,事实上任何一个元素都有完整的一套CSS样式,这意味着一个元素所有的CSS属性都必须要有值。浏览器为了把一个元素渲染在页面上,必须拥有所有的CSS样式。


而在我们平时书写CSS文件时只会写必要的部分,例如文章开头H1元素的color属性,这是我希望它与默认样式不同的地方。这会让我们意识不到其实它除了color属性之外还有其他所有的CSS属性,而这些我们没有声明的属性大概率是在使用其默认值。


为什么是大概率呢?难道还存在没有声明的属性值不使用默认值的情况?有的,这就是为什么你需要了解CSS中属性的计算过程。


总的来说,对某个HTML元素而言,其所有的CSS属性值从无到有都需要经历下面这四个步骤,


  1. 确定声明值
  2. 层叠冲突
  3. 使用继承
  4. 使用默认值 


确定声明值:参考样式表中没有冲突的声明,作为CSS属性值


首先第一步,是确定声明值。所谓声明值就是作者自己所书写的 CSS 样式,例如前面的:


h1 {
  color: red;
}


这里我们声明了 h1 元素为红色,那么就会应用此属性设置。


当然,除了作者样式表,一般浏览器还会存在“用户代理样式表”,简单来讲就是浏览器内置了一套样式表。


h1 {                                          用户代理样式表
  display: block;    
  font-size: 2em;    
  margin-block-start: 0.67em;    
  margin-block-end: 0.67em;    
  margin-inline-start: 0px;    
  margin-inline-end: 0px;    
  font-weight: bold;    
  unicode-bidi: isolate;
}


在上面的示例中,作者样式表中设置了 color 属性,而用户代理样式表(浏览器提供的样式表)中设置了诸如 display、margin-block-start、margin-block-end、margin-inline-start、margin-inline-end 等属性对应的值。


这些值目前来讲也没有什么冲突,因此最终就会应用这些属性值。


层叠冲突:对样式表有冲突的声明使用层叠规则,确定CSS属性值


还是上面的例子,假设我们要给h1元素设置font-size: 10em,此时作者样式表和浏览器样式表中都有对h1元素font-size属性的声明,这就产生了冲突。


在我们平时的开发中也会有这种情况,我们写的多个CSS选择器都选择到了这个元素,并为其某个属性赋值,那么对于这个属性值,浏览器该用哪一个呢?


这就像我们玩的吃鸡游戏一样,我们需要通过大乱斗决出最后存活的人。而大乱斗的过程,CSS称呼它为层叠,层叠的规则按优先级从高到低可以分为如下三个步骤:


  1. 比较重要性
  2. 比较明确性
  3. 比较资源顺序


第一步,比较重要性。


当不同的 CSS 样式来源拥有相同的声明时,此时就会根据来源的重要性来确定应用哪一条样式规则。


  • 作者样式表中的!important样式。
  • 作者样式表中的普通样式。
  • 浏览器默认的用户代理样式。


对应的重要性顺序依次为:作者样式表中的!important样式 > 作者样式表中的普通样式 > 用户代理样式


第二步,比较明确性。


如果在同一个样式表中有声明冲突怎么办呢?此时会进行声明的明确性比较。


.test h1{
  font-size: 50px;
}

h1 {
  font-size: 20px;
}


例如上面代码都来源于作者样式表,源的重要性是相同的,此时就要选择最明确的那一个。那么明确性如何比较呢?


CSS的选择器所选择的范围是不同的,一般情况下,用元素选择器在一个网页中选择范围是特别大的,因此是不明确的。而ID选择器一般只能选到对应ID的那个元素,因此是很明确的。


但是只靠感觉是不行的,我们需要有一套标准去确定一个选择器的明确性,为了直观的表现明确性,我们得用上选择器的权重的概念。


选择器的权重又如何计算呢?我们可以把权重值看成一个四位数。


千位:如果是内联样式(直接在元素的style中书写),得一分。
百位:选择器每包含一个ID选择,则加一分
十位:选择器每包含一个类选择器或属性选择器或伪类选择器,则加一分
个位:选择器每包含一个元素选择器或伪元素选择器,则加一分


例如,上面的.test h1选择器包含一个类选择器和一个元素选择器,得0011分。h1选择器只有一个元素选择器,得0001分,因此.test h1选择器更明确。


再来计算几个选择器试试看吧!


h1 + p::first-letter 选择器得多少分呢?h1是元素选择器,得一分。+号是Next-Sibling选择器,意思是选择下一个兄弟元素,注意,像空格、+、>、~这样的关系选择器是不得分的。p是元素选择器,得一分,::first-letter是伪元素选择器,得一分,最后是0003分。


li > a[href*="en-US"] > .inline-warning 选择器得多少分呢?你能算出来这个答案吗?可以在评论区回复哦。


在浏览器的开发者工具中,选中一个元素后,在样式里把鼠标悬停在对应的选择器上,可以看到一个明确性:(0,1,1)的提示。其实内联样式是一种特殊情况,为了帮助我们更好的记住这种特殊情况,我们可以把它当作千位来记。


p3



最后一步,比较资源顺序。


经过上面两个步骤,大多数的属性值已经可以被确定下来了。但是还剩下一种情况,那就是声明既是同源,权重也相同。此时就需要根据资源的顺序决定。举个例子,


h1 {
  font-size: 50px;
}

h1 {
  font-size: 20px;
}


上面代码就是既是同源,权重也相同,此时位于下面的样式声明会层叠掉上面的,最后会应用font-size: 20px这个属性值。


经过上面的计算,样式声明中存在冲突的情况就解决了,大吉大利,今晚吃鸡!


使用继承:对于仍然没有值的属性,若可以继承,则继承父元素的值


层叠冲突这一步完成后,解决了样式表中的声明冲突问题。那么如果该属性没有在样式表中声明呢?直接使用默认值吗?


不,不,不,对于那些可以继承的属性,我们还可以通过继承来获得属性值。比如,


<head>
  <style>
    div {
      color: red;
      border: 2px solid #ccc;
      padding: 1em;
    }
  </style>
</head>
<body>
  <div>
    <p>Lorem ipsum dolor sit amet.</p>
  </div>
</body>


上面代码的作者样式表选择了所有的div元素并声明了三个属性,分别是color, border, padding,而p元素中则没有声明任何属性。那么请问,p元素的color, border, padding属性分别是 ? 我们来试试。


p4


可以看到p元素继承了div元素的color属性,但没有继承border和padding属性,因为这两个属性不能被继承,我们可以在MDN中查看属性是否是继承属性。如果border和padding可以被继承,每个p元素都会获得一个边框,而这可能就不是我们想要的结果,这会让css变得难以维护。


由于 color 是可以继承的,因此 p 元素会从父元素的同属性的计算值继承到 color 属性的值。


使用默认值:对于仍然没有值的属性,使用默认值


目前走到这一步,如果属性值都还不能确定下来,那么就只能是使用默认值了。


前面我们也说过,一个 HTML 元素要在浏览器中渲染出来,必须具备所有的 CSS 属性值,但是绝大部分我们是不会去设置的,用户代理样式表里面也不会去设置,也无法从继承拿到,因此最终都是用默认值。


好了,这就是关于 CSS 属性计算过程的所有知识了。


实际工作中碰到的问题


接下来给大家介绍一个我在搭建这个博客时遇到的一个实际的问题,如果我不了解CSS属性计算过程,那么我可能挠破头都无法解决这个BUG。


先介绍一下问题的背景,Helloeven使用Trix(一个富文本编辑器)插件来实现用富文本展现博客的内容。而博客使用的UI框架是Tailwind CSS。问题的根源就是这个Trix插件提供的CSS与我为Helloeven定制的Tailwind CSS所产生的层叠冲突的问题,为了方便你理解我把他们简化一下。


我想要实现的功能是博客内容中的pre元素(一般用于展示代码)能够根据网站的暗黑模式改变背景颜色,为了实现这个功能我需要在Tailwind CSS中实现下面代码,


/* tailwind.css 中是我定制的代码 */

.trix-content pre {
  @apply bg-gray-200 dark:bg-gray-800 p-4 rounded-md;
}

/* application.css 中是引入的Trix的CSS */
.trix-content pre {
  display: inline-block;    
  width: 100%;    
  vertical-align: top;    
  font-family: monospace;    
  font-size: 0.9em;    
  padding: 0.5em;    
  white-space: pre;    
  background-color: #eee;    
  overflow-x: auto;
}


不出意外的果然出意外了,pre中的背景颜色没有更改成功,还是Trix插件的background-color: #eee;


p5



刚遇到这个问题有点懵,但不要着急,只要按着本博客中所说的流程一步步,很快就能找到原因。


第一步,确定声明值,很明显样式表中的声明产生了冲突,我们需要通过层叠来解决。


第二步,层叠冲突。


1. 比较重要性。两个对于pre元素background-color属性的声明都是出自作者样式表,即application.css和tailwind.css。来源的重要性是一样的。


2. 比较明确性。我直接把鼠标悬停到三个选择器上,发现三个选择器的权重都是(0,1,1),且它们都不是内联样式。因此他们明确性也是一致的。


3. 比较资源顺序。那么问题就是出在这一步了,我打开项目中引入这两个CSS的layout文件中,发现果然是先引入的tailwind.css再引入的application.css,这就导致了我自定义的CSS无法层叠过插件自带的CSS,这不是我想要的。所以只需要更换引入的顺序即可。


BUG修复完成,心满意足,收工!

评论区 (0)

不需要刷新页面就能看到最新的评论哦~ 快试试吧!

发布评论