前端必经之路之CSS底层原理(剖析CSS权重、CSS布局模型和BFC等重要底层知识)

     对于前端开发人员来说,最容易忽视的莫过于对css底层原理的学习了。在我们进行前端页面的开发过程中,始终离不开对css的使用。css的全称为层叠样式表(cascading style sheets),它能够对网页的布局、颜色、背景、宽高、字体等显示效果进行控制,让我们的网页变得更加漂亮美观。一开始接触css,你或许会感觉很容易,但随着开发经验的积累和学习的深入你会发现,css其实非常的复杂。css的许多特性一直容易被人忽略,如果你在编写css过程中总是无法实现自己想要的效果却又找不出原因所在,那你是时候来深入了解一下css的底层知识了。

     在编辑html文档时,我们引入css的方式主要有三种,即行间样式、文档<style>标签的编辑和引入外部css文件。当我们想通过<style>标签或外部css文件对HTML元素样式进行设置时,我们就需要使用到css选择器。

      css选择器种类非常多,它们大体上分为八类,即标签选择器、类选择器、id选择器、通配符选择器、属性选择器、伪类选择器、伪元素选择器和组合选择器。

选择器

例子

标签选择器

如div、span、p

类选择器

如.wrapper、.nav、.footer

id选择器

如#name、#game、#music

通配符选择器

由符号*表示

属性选择器

如a[src^="https"]、a[src$=".pdf"]、a[src*="rabbit"]

伪类选择器

如p:only-child、p:nth-last-child(2)、p:nth-of-type(2)

伪元素选择器

如div::before、div::after、p::first-letter

组合选择器

如div p、div>p、div+p

其它选择器及详细使用说明请参照官方文档
http://www.w3school.com.cn/cssref/css_selectors.asp

      我们可以通过各种各样的css选择器对元素样式进行操作,但是在前端开发过程中,有时候我们会发现自己写的css代码不能生效,尤其是当我们引入css外部文件后,越容易出现css代码失效的问题。而这个问题的根源,就在于css一个重要的底层概念——权重

     css有三大特性:继承性、层叠性、优先级。当我们编辑css样式时,根据css的层叠性,我们通常会认为当出现相同属性样式作用于同一个元素时,最后定义的规则会覆盖前面定义的规则。例如,当我们在css文件前面先给p标签的font-size设置为15px,然后再在后面把p标签的font-size设置为20px,这样后面的20px就会覆盖前面的15px。不过,当你在实际开发过程中,你会发现这种情况并不总是成立。这种覆盖规则,只会出现在当两个样式之间权重相等的时候。

     css权重也叫css优先级(css specificity),它是一种css设定的匹配规则,浏览器通过设定好的优先级来判断哪些属性值与DOM元素最为相关,从而在该DOM元素上应用这些样式值。不同的选择器对应不同的权重级别,优先级高的属性样式会率先被采用。

      通常,css权重的优先级顺序如下所示:
!important > 行间样式 > id选择器 > 类选择器 | 属性选择器 | 伪类选择器 > 标签选择器 | 伪元素选择器 > 通配符选择器 > 继承的样式

      它们各自的权重值如下表所示:

类别

权重值

!important

Infinity(无限,最高级别)

行间样式

1000

id选择器

100

类选择器 | 属性选择器 | 伪类选择器

10

标签选择器 | 伪元素选择器

1

通配符选择器

0

通过继承获得的样式

没有权重

      权重值是可以叠加的,当你同时运用了多个选择器选定某个标签,该标签下的属性样式的权重就是这多个选择器权重之和,如图所示:

     在多数情况下,我们根据10进制的计算方法对权重值进行相加,就可以得到我们想要的比较结果。可权重值的底层设定并不是基于10进制的,而是基于256进制。所以即使我们写了一个包含11个标签选择器的组合选择器,其权重值按十进制相加结果虽然为11,也比不上一个权重值为10的类选择器。按照256进制,数值只有达到255并再加1的时候才能进位。也就是说,1个class选择器的权重值等于256个标签选择器权重值之和。

     相同权重的样式应用,不仅满足层叠性(即后面定义的样式会覆盖前面定义的样式),也满足就近原则。也就是说,在权重值相等的情况下,当你在外部css文件定义了p标签的font-size样式,而在html文档的<style>标签也定义了p标签的font-size样式,那么p标签会优先采取离它本身最近的<style>标签里定义的font-size样式。

     由于css的覆盖原则,所以当我们对a标签进行伪类样式设定时,就需要遵循LVHA原则。所谓LVHA,分别代表四个经常用于a标签的伪类选择器,其用法和功能如图所示:

                          

     在权重相等的情况下,我们对a标签这四个伪类选择器的排序,必须按照a:link 、a:visited、a:hover、a:active的先后顺序进行排列。这样当a标签未访问过时,它可以正常的显示a:link和a:hover样式;当a标签被触发和访问时,它又可以正常的显示a:active和a:visited样式,同时不影响触发过后的a:hover样式。

     伪类选择器中有一个例外:否定伪类选择器:not(),它在优先级计算中并不会被当作是伪类。它的权重值不是10,而是和标签选择器与伪元素选择器一样都为1。

(注意:伪元素选择器虽然可以对html进行操作,但是伪元素没有元素结构)

     大多数浏览器的一些标签都会有默认的margin值或者padding值,我们经常使用通配符选择器* 将所有标签的margin值和padding值都设为0。使用通配符选择器* 的好处在于它的权重值是0,不会影响后续其它标签对它们各自样式值的修改。

                     

      无论其它样式声明被赋予多大的权重,当遇到带有!important规则声明的样式时,该样式声明会覆盖其它任何的样式。所以!important的权重值通常也被认为是Infinity(无限)。当遇到两条相互冲突且都带有!important 规则的样式声明,拥有更大优先级的样式声明将会被优先采用。
(遇到样式覆盖时不要轻易使用!important,应该优先考虑从样式的级联属性或者位置来解决问题)

     权重涉及的知识大体上就这些,接下来为大家讲讲css三种元素类型。

      元素是文档结构的基础,在html文档中,每个元素生成了一个包含了该元素内容的矩形框,称为元素盒子(element box)。每个盒子除了有自己大小和位置外,还影响着其他盒子的大小和位置(关于css两种盒模型留待以后讲解)。不同的元素显示方式会有所不同,css的元素可以分为三类:块级元素(block-level)、行内元素(inline-level,也叫内联元素)和行内块元素(inline-block level)。

     块级元素无论其元素内容的多少或者把它的宽度设置多小,它都会默认独占其父元素一整行的内容区域,块级元素的默认宽度为父元素的整个宽度,常见的块级元素有<div>、<p>、<h1>元素等。而行内元素并不会独占一行,可以和其他行内元素同处一行,它的大小默认为其元素内容的大小。同一行可以包含多个行内元素,常见的行内元素有<img>、<span>元素等。

     当元素是块级元素时,我们可以自由的改变其宽高,或为它们设置margin值和padding值。不过当元素是行内元素时,我们为它们设定的宽高和竖直方向上的边距值都会失效。行内块元素则同时具备了块级元素和行内元素的特点,元素宽度和元素内容宽度保持一致,且它们的宽高、竖直边距和行高都可以进行设置,我们可以通过将元素的display属性设为inline-block将其变为行内块元素。其实元素的结构属性都是可以通过设置特定css样式进行转换的,块级元素可以变为行内元素,行内元素也可以变为块级元素。

     讲完这三种元素,就可以来讲讲css的布局模型了。css的布局模型是css最基本、最核心的概念之一,在网页中,元素共有三种布局模型,分别为流动模型(Flow)、浮动模型(Float)和层模型(Layer)。

     流动模型:流动布局模型有两个特征:①块级元素会在所处的父级元素内自上而下按顺序垂直排列 ②行内元素会在所处的父级元素内从左到右水平按顺序水平排列。

     浮动模型:通过css的float属性可以将元素设置为浮动元素。元素浮动之后不再占据原来的位置,它们会尽可能的往包裹它们的父元素的左边框或右边框靠,并会在它们元素所处位置的下面产生浮动流,影响下面的元素定位。浮动元素并没有完全脱离文档流,它只是从包裹它的盒子中浮动起来并尽可能远的往左侧或者右侧进行移动。浮动设计的初衷是为了实现文字在图片周围的环绕效果,这个留到文章后面再解释。

(浮动元素产生了浮动流,对于产生了浮动流的元素,块级元素看不到它们,这就是造成了margin塌陷的直接原因。只有产生了bfc的元素和具有文本特性的元素(凡是带有inline的元素都有文本特性,如p、strong、span)以及文本元素(文字本身)能看到浮动元素。关于margin塌陷和bfc,文章后面会做详细讲解。)

     层模型:层模型就相对比较复杂了,它拥有三种定位形式,分别相对定位(position: relative)、 绝对定位(position: absolute)和固定定位(position: fixed)。

相对定位

       relative表示相对定位,它通过left、right、top、bottom属性确定元素在正常文档流中的偏移位置。相对定位是相对于元素本身以前的位置移动,偏移前的位置保留不动。在使用相对定位时,就算元素被偏移了,但是他仍然占据着它没偏移前的空间。

绝对定位

        absolute表示绝对定位,它将元素从文档流中脱离出来,然后使用left、right、top、bottom属性相对于与其最接近的一个具有定位属性的父级元素进行绝对定位。如果不存在这样的父级元素,则相对于body元素,即浏览器窗口进行定位。通常在我们为元素设定绝对定位absolute进行位置调整时,我们会先给它的父级元素设置相对定位relative,这样父元素还占据着位置,而子元素可以在父元素内自由移动。被设置了绝对定位的元素可以覆盖在它后面其他元素的上方。它们在文档流中是不占据空间的,仍然在文档流中的其他元素将忽略该元素并填补它原先的空间。如果某元素设置了绝对定位,那么它在文档流中的位置将会被删除。

(float和absolute 产生的效果都类似于脱离文档流,但是两个float的盒子是彼此看得见彼此的,即无法发生重叠,而两个absolute的盒子可以重叠,这侧面说明了float并没有真正脱离文档流)

 固定定位

       fixed表示固定定位,与absolute定位类型类似,同样会脱离文档流,不占据原来的位置。但它的相对移动的坐标是视口(浏览器窗口)本身。由于视口本身是固定的,它不会随浏览器窗口的滚动条滚动而变化,除非你在屏幕中移动浏览器窗口的屏幕位置,或改变浏览器窗口的显示大小。因此固定定位的元素会始终位于浏览器窗口内的某个位置,不会受文档流动影响。

(其实float:left/right 和position:absolute/fixed都会把元素从内部隐式变成行内块元素,但控制台显示的依然是原来的display属性。)


     花了那么多时间讲css这三类元素和三类布局,主要是为了接下来要讲的一个概念做铺垫 —— BFC。BFC可以说是css非常难的一个知识点了,网上大多数资料的表述都不够准确,官方文档给的解释又很模糊。为了写好这个知识点,自己可谓是费尽心力了。
我们先来看看BFC的概念:
     BFC的全称叫块级格式化上下文 (Block Fromatting Context),是web页面的可视化css渲染出的一部分。它是块级盒布局出现的区域,也是浮动层元素进行交互的区域。该区域拥有一套渲染规则来约束块级盒子的布局。它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。当涉及到可视化布局的时候,BFC提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。触发了BFC的盒子是一个独立的布局环境,它的子元素布局不受外界影响。

     BFC对于定位与清除浮动很重要。定位和清除浮动的样式规则只适用于处于同一块格式化上下文内的元素。浮动不会影响其它块格式化上下文中元素的布局,并且清除浮动只能清除同一块BFC区域中在它前面的元素的浮动。一个BFC包括创建它的元素内部所有内容,除了被包含于创建新的BFC的后代元素内的元素。

     毫无疑问,从概念角度来理解BFC这个知识点,实在是太难了。我们倒不如换个角度,从它的功能和特性上来理解什么是BFC。

     首先我们要了解怎样创建一个元素的BFC,触发BFC的情况大体上有以下几种:

  • 根元素或其它包含它的元素
  • float属性不为none的时候
  • overflow属性不为visible的时候
  • display属性为inline-block、table-cell、table-caption、flex、inline-flex的时候
  • position属性为absolute或fixed的时候

     我们现在知道了BFC的触发方式,那BFC到底有什么用呢?要知道BFC的用途,我们得先了解开发团队给BFC制定的规则。BFC具有以下的布局规则:
1. 触发了BFC的元素,其子元素的布局满足流模型,并沿着父元素的边框排列。
2. 处于同一个BFC下的两个相邻盒子的margin值会重叠(这种情况叫做margin合并)。
3. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之也如此。
4. BFC的区域会和浮动元素产生边界,不会与浮动元素重叠。
5. 在计算生成了BFC的元素的高度时,其浮动子元素也参与计算(解决margin塌陷)。

(参考资料: https://www.sitepoint.com/understanding-block-formatting-contexts-in-css/)

     讲了这么多,估计还是有很多朋友处于一头雾水的状态,我们就直接通过举例子,来看看BFC究竟如何产生影响以及起到什么作用。

1.防止盒子间外边距margin重叠(margin合并)
      我们首先来看一下这段代码

     当你运行了这段代码,你会发现,虽然你给box2添加了margin-bottom,给box3添加了margin-top,但box2和box3之间的距离并没有相加变成80px,而还是40px。原因就在于处于同一个BFC区域下的盒子(此时的BFC触发来源于根元素),它们在垂直方向上的margin值会发生重叠,取它们之间绝对值最大的值(如果一正一负就取两者相加之和)。要解决这个问题,我们可以单独给其中一个盒子或两个盒子各自创建新的BFC环境,如图所示:

方式一:

方式二:

     由于第二种方式解决margin重叠的方式需要操作到dom结构,因此并不建议这么做,而是采用第一种方式或直接人为在数值上对margin进行操作,即直接对一个元素设定两个元素之间想要的margin值。
(第一种方式经过测试发现overflow属性并不能解决margin重叠的问题,具体原因留待以后研究)
     除了相邻元素之间会发生margin重叠,父元素与它的第一个子元素也会发生margin重叠,并且始终使第一个子元素的margin-top失效,如图所示


     这张图我们可以看到,在同一个BFC环境下,wrapper盒子虽然距离它的父级container顶部偏移了10px,可wrapper盒子的第一子元素box1并未对wrapper顶部偏移8px,只有box2的margin生效了。(重叠只发生在父级wrapper和box1上,所以box2的margin还是8px而不是重叠后的10px)


     当我们把子元素的margin-top改为15px后,我们发现wrapper也相对它的父级偏移了15px,此时便发生了margin重叠并作用于父级盒子上,而wrapper下面的第一个元素照样没有对wrapper偏移。

     要改变这种情况,同样只需要触发盒子的BFC就够了,不过是触发父元素的BFC,而不是第一个子元素的BFC。


     此时wrapper的margin-top还原为它本身的10px,而它的第一个子元素成功在wrapper盒子内向下偏移了。
     其实还有另一种方法也能够解决此处的margin重叠,我们只要给父级wrapper加上border或border-top就够了。

      这里的border一定要有具体的像素,但它和我们今天讲的BFC没有什么特别的关系。

2.防止与浮动元素重叠
     我们先定义两个盒子,当我们给其中一个盒子加了float属性后,两个盒子便会叠在一起


     原因是块级盒子看不到浮动元素(但文本可以看到浮动元素,所以图片中的2字被挤了出来),想要让块级盒子看到浮动元素不与浮动元素重叠,我们只要触发它的BFC。

     这个方法也常常被应用在网页的两栏布局上。

(注意:对于未触发bfc的盒子,使用float和position:absolute 可以叠在它们上面,但前提是所要叠的盒子位于使用了float或position:absolute的盒子后面)

3.包裹住浮动元素(解决margin塌陷)
     前面讲到浮动的时候已经说过,块级元素是看不到浮动元素的。所以当一个块级盒子包裹一个浮动元素时,块级盒子无法将该浮动元素包裹住,如图所示:


     图中的wrapper并没有包裹住子元素box,这种情况叫做margin塌陷。面对这种情况,我们只要触发父盒子wrapper的BFC,浮动元素对它来说就可见了。


     我们通过overflow属性触发了父盒子的BFC,内部的浮动元素便成功的把父级元素撑开了。而假如我们使用的是display:inline-block的方法来触发BFC,父级元素还会展现行内块元素的特点,以子元素的宽高决定父元素的宽高,如图所示


     其实,这里如果是通过float或position:absolute的方法去触发wrapper的BFC,也会产生同样的效果。因为加了float或position:absolute的元素,会从内部隐式的把盒子变成行内块元素,虽然我们无法从控制台看到wrapper盒子display属性的改变。

     解决margin塌陷的方法,除了触发父级盒子的BFC或直接人为的给父级盒子加上适当的高度在视觉上包裹住子元素,其实还有另外一种方法,那就是在产生浮动流元素的下面清除浮动。


     清楚浮动使用的是css样式属性中的clear:both,这个属性是专门用来清楚浮动的,它有三个可选参数:left、right、和both,但我们一般都默认使用both。我们都知道浮动流只能影响它下面的元素,所以清楚浮动的样式要写在浮动元素后面的位置。
     因为这种写法会增加html中的dom元素,为了不改变dom结构,我们不推荐使用这种写法清除浮动,而是使用css2新增的伪元素选择器 :after。


     利用伪元素清除浮动要注意两点:①伪元素的使用必须给它设置content,我们一般把content设为空就可以。②清除浮动的元素必须是块级元素,所以我们要给伪元素设置display:block的属性。


     伪元素不会改变html的dom结构,但又可以对dom结构进行操作,所以用伪元素清除浮动是一个很不错的方法。

4. 实现文本对图片的环绕效果
     float这个属性的设计初衷,一开始就是为了实现文字对图片的环绕效果。我们先在html里插入一张图片,并在图片周围写上一些文字:


     你会发现这些文字最开始会与图片底部对齐,而上面留着一大块空白。若我们想让文字填满上面的空白区域,我们只需要给图片加上float属性。


     当你给image加上float属性时,你会发现父级盒子wrapper会形成margin塌陷的效果,因为块级元素看不到浮动元素,这里有点类似反向利用了margin塌陷的缺点。
     不过,其实在这里把图片文字环绕效果归结在BFC的应用上并不合适。实际上,哪怕你触发了父级盒子的BFC解决了margin塌陷,文本还是会照样环绕在图片周围。这个地方侧面说明了图片文字环绕效果主要还是文本与浮动流元素之间的关系,因为产生了浮动流的元素能影响底下元素的布局,浮动元素自身的高度低于相邻元素的文本的高度,而文本又能看到浮动流元素,它的环绕与BFC的关系不大。

缩小父级宽度,文本环绕的效果就更清晰了:

     熬了三天两夜的时间,终于将这篇文章写完了。总结一下心得,只想说对底层原理的探索真的很不容易。查阅了很多相关资料,发现网上的说法不一,许多还是要靠自己一点点去尝试和验证。平时我们可能学了很多知识,但是当有人问起你具体原理的时候,你可能会发现自己虽然能理解,但是却怎么也讲不出来。这就是写技术推文的好处,它能帮你重新捋一遍自己学过的知识点,将各个知识点串联起来,并清晰准确的表达出来。
     如果要我给编程学习者一点建议的话,我想说平日里除了要积累适当的编程经验,还要腾出时间去拜读一些权威的书籍和文章。看书能引领你更加全面深入的去学习你所要从事的行业,同时也能扩宽你的知识面,提高你知识的广度和深度。知识体系只有学得足够深入,你才能摆脱被不断更新的应用型、API型知识牵着鼻子走的局面,才能在不断更新发展的行业里有立足之地。如果平时编程只会套用jquery做做页面,写写各种curd,那么可能会导致你在这个行业做5年和做10年都没什么区别。这就是为何中国那么多程序员做到35岁就选择转行,而国外很多程序员50多岁还是很普遍的原因。想在技术的道路上有所作为,就一定要对自己的能力严格要求、精益求精。
      长路漫漫,愿你我不忘初心。路漫漫其修远兮,吾将上下而求索。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值