CSS 和 HTML 是網頁開發中布局相關的組成部分,涉及的內容比較多和雜亂,本小節重點介紹下常考的知識點。
## 知識點梳理
* 選擇器的權重和優先級
* 盒模型
* 盒子大小計算
* margin 的重疊計算
* 浮動`float`
* 浮動布局概念
* 清理浮動
* 定位`position`
* 文檔流概念
* 定位分類
* fixed 定位特點
* 絕對定位計算方式
* `flex`布局
* 如何實現居中對齊?
* 理解語義化
* CSS3 動畫
* 重繪和回流
* * *
## 選擇器的權重和優先級
CSS 選擇器有很多,不同的選擇器的權重和優先級不一樣,對于一個元素,如果存在多個選擇器,那么就需要根據權重來計算其優先級。
權重分為四級,分別是:
1. 代表內聯樣式,如`style="xxx"`,權值為 1000;
2. 代表 ID 選擇器,如`#content`,權值為 100;
3. 代表類、偽類和屬性選擇器,如`.content`、`:hover`、`[attribute]`,權值為 10;
4. 代表元素選擇器和偽元素選擇器,如`div`、`p`,權值為 1。
**需要注意的是:通用選擇器(\*)、子選擇器(>)和相鄰同胞選擇器(+)并不在這四個等級中,所以他們的權值都為 0**。 權重值大的選擇器其優先級也高,相同權重的優先級又遵循后定義覆蓋前面定義的情況。
## 盒模型
### 什么是“盒子”
初學 CSS 的朋友,一開始學 CSS 基礎知識的時候一定學過`padding` `border`和`margin`,即內邊距、邊框和外邊距。它們三者就構成了一個“盒子”。就像我們收到的快遞,本來買了一部小小的手機,收到的卻是那么大一個盒子。因為手機白色的包裝盒和手機機器之間有間隔層(內邊距),手機白色盒子有厚度,雖然很薄(邊框),盒子和快遞箱子之間還有一層泡沫板(外邊距)。這就是一個典型的盒子。

如上圖,真正的內容就是這些文字,文字外圍有 10px 的內邊距,5px 的邊框,10px 的外邊距。看到盒子了吧?
> 題目:盒子模型的寬度如何計算
### 固定寬度的盒子
```
<div style="padding:10px; border:5px solid blue; margin: 10px; width:300px;">
之前看過一篇文章,叫做《瀏覽器工作原理:新式網絡瀏覽器幕后揭秘》,
文章言簡意賅的介紹的瀏覽器的工作過程,web前端
</div>
```

如上圖,得到網頁效果之后,我們可以用截圖工具來量一下文字內容的寬度。發現,文字內容的寬度剛好是 300px,也就是我們設置的寬度。
因此,**在盒子模型中,我們設置的寬度都是內容寬度,不是整個盒子的寬度。而整個盒子的寬度是:(內容寬度 + `border`寬度 + `padding`寬度 + `margin`寬度)之和**。這樣我們改四個中的其中一個,都會導致盒子寬度的改變。這對我們來說不友好。
沒關系,這個東西不友好早就有人發現了,而且已經解決,下文再說。
### 充滿父容器的盒子
默認情況下,`div`是`display:block`,寬度會充滿整個父容器。如下圖:
```
<div style="padding:10px; border:5px solid blue; margin: 10px; width:300px;">
之前看過一篇文章,叫做《瀏覽器工作原理:新式網絡瀏覽器幕后揭秘》,
文章言簡意賅的介紹的瀏覽器的工作過程,web前端
之前看過一篇文章,叫做《瀏覽器工作原理:新式網絡瀏覽器幕后揭秘》,
文章言簡意賅的介紹的瀏覽器的工作過程,web前端
</div>
```

但是別忘記,這個 div 是個盒子模型,它的整個寬度包括(內容寬度 + `border`寬度 + `padding`寬度 + `margin`寬度),整個的寬度充滿父容器。
問題就在這里。如果父容器寬度不變,我們手動增大`margin`、`border`或`padding`其中一項的寬度值,都會導致內容寬度的減少。極端情況下,如果內容的寬度壓縮到不能再壓縮了(例如一個字的寬度),那么瀏覽器會強迫增加父容器的寬度。這可不是我們想要看到的。
### 包裹內容的盒子
這種情況下比較簡單,內容的寬度按照內容計算,盒子的寬度將在內容寬度的基礎上再增加(`padding`寬度 + `border`寬度 + `margin`寬度)之和。
```
<div style="padding:10px; border:5px solid blue; margin: 10px; width:300px;">
之前看過一篇文章,叫做《瀏覽器工作原理:新式網絡瀏覽器幕后揭秘》
</div>
```

### `box-sizing:border-box`
前面提到,為盒子模型設置寬度,結果只是設置了內容的寬度,這個不合理。如何解決這一問題?答案就是為盒子指定樣式:**`box-sizing:border-box`**。
```
<div style="padding:10px; border:5px solid blue; margin: 10px; width:300px; box-sizing:border-box;">
之前看過一篇文章,叫做《瀏覽器工作原理:新式網絡瀏覽器幕后揭秘》
</div>
```

上圖中,為`div`設置了`box-sizing:border-box`之后,300px 的**寬度是內容 + `padding` + 邊框的寬度(不包括`margin`)**,這樣就比較符合我們的實際要求了。建議大家在為系統寫 CSS 時候,第一個樣式是:
```
* {
box-sizing:border-box;
}
```
大名鼎鼎的 Bootstrap 也把`box-sizing:border-box`加入到它的`*`選擇器中,我們為什么不這樣做呢?
### 縱向 margin 重疊
這里提到 margin,就不得不提一下 margin 的這一特性——縱向重疊。如`<p>`的縱向 margin 是 16px,那么兩個`<p>`之間縱向的距離是多少?—— 按常理來說應該是 16 + 16 = 32px,但是答案仍然是 16px。因為縱向的 margin 是會重疊的,如果兩者不一樣大的話,大的會把小的“吃掉”。
* * *
## 浮動`float`
float 用于網頁布局比較多,使用起來也比較簡單,這里總結了一些比較重要、需要注意的知識點,供大家參考。
### 誤解和誤用
float 被設計出來的初衷是用于**文字環繞效果**,即一個圖片一段文字,圖片`float:left`之后,文字會環繞圖片。
```
<div>
<img src="image/1.png" style="float:left">
一段文字一段文字一段文字一段文字一段文字一段文字一段文字一段文字一段文字
</div>
```
但是,后來大家發現結合`float + div`可以實現之前通過`table`實現的網頁布局,因此就被“誤用”于網頁布局了。
> 題目:為何 float 會導致父元素塌陷?
### 破壞性

float 的**破壞性** —— float 破壞了父標簽的原本結構,使得父標簽出現了坍塌現象。導致這一現象的最根本原因在于:**被設置了 float 的元素會脫離文檔流**。其根本原因在于 float 的設計初衷是解決文字環繞圖片的問題。大家要記住 float 的這個影響。
### 包裹性
**包裹性**也是 float 的一個非常重要的特性,大家用 float 時一定要熟知這一特性。咱們還是先從一個小例子看起:

如上圖,普通的 div 如果沒有設置寬度,它會撐滿整個屏幕,在之前的盒子模型那一節也講到過。而如果給 div 增加`float:left`之后,它突然變得緊湊了,寬度發生了變化,把內容中的三個字包裹了——這就是包裹性。為 div 設置了 float 之后,其寬度會自動調整為包裹住內容寬度,而不是撐滿整個父容器。
注意,此時 div 雖然體現了包裹性,但是它的 display 樣式是沒有變化的,還是`display: block`。
float 為什么要具有包裹性?其實答案還是得從 float 的設計初衷來尋找,float 是被設計用于實現文字環繞效果的。文字環繞圖片比較好理解,但是如果想要讓文字環繞一個 div 呢?此時 div 不被“包裹”起來的話,就無法實現環繞效果了。
### 清空格
float 還有一個大家可能不是很熟悉的特性——清空格。按照慣例,咱還是先舉例子說明。
```
<div style="border: 2px solid blue; padding:3px;">
<img src="image/1.png"/>
<img src="image/2.png"/>
<img src="image/3.png"/>
<img src="image/4.png"/>
</div>
```

加上`float:left`之后:

上面第一張圖中,正常的 img 中間是會有空格的,因為多個 img 標簽會有換行,而瀏覽器識別換行為空格,這也是很正常的。第二張圖中,為 img 增加了`float:left`的樣式,這就使得 img 之間沒有了空格,4 個 img 緊緊挨著。
如果大家之前沒注意,現在想想之前寫過的程序,是不是有這個特性。為什么 float 適合用于網頁排版(俗稱“砌磚頭”)?就是因為 float 排版出來的網頁嚴絲合縫,中間連個蒼蠅都飛不進去。
“清空格”這一特性的根本原因是 float 會導致節點脫離文檔流結構。它都不屬于文檔流結構了,那么它身邊的什么換行、空格就都和它沒了關系,它就盡量往一邊靠攏,能靠多近就靠多近,這就是清空格的本質。
> 題目:手寫 clearfix
### `clearfix`
清除浮動的影響,一般使用的樣式如下,統稱`clearfix`代碼。所有 float 元素的父容器,一般情況下都應該加`clearfix`這個 class。
```
.clearfix:after {
content: '';
display: table;
clear: both;
}
.clearfix {
*zoom: 1; /* 兼容 IE 低版本 */
}
```
```
<div class="clearfix">
<img src="image/1.png" style="float: left"/>
<img src="image/2.png" style="float: left"/>
</div>
```
### 小結
float 的設計初衷是解決文字環繞圖片的問題,后來誤打誤撞用于做布局,因此有許多不合適或者需要注意的地方,上文基本都講到了需要的知識點。如果是剛開始接觸 float 的同學,學完上面的基礎知識之后,還應該做一些練習實戰一下 —— 經典的“圣杯布局”和“雙飛翼布局”。這里就不再展開講了,網上資料非常多,例如[淺談面試中常考的兩種經典布局——圣杯與雙飛翼](https://juejin.im/entry/5a8868cdf265da4e7e10c133?utm_source=gold_browser_extension)(此文的最后兩張圖清晰地展示了這兩種布局)。
* * *
## 定位`position`
position 用于網頁元素的定位,可設置 static/relative/absolute/fixed 這些值,其中 static 是默認值,不用介紹。
> 題目:relative 和 absolute 有何區別?
### relative
相對定位 relative 可以用一個例子很輕松地演示出來。例如我們寫 4 個`<p>`,出來的樣子大家不用看也能知道。
```
<p>第一段文字</p>
<p>第二段文字</p>
<p>第三段文字</p>
<p>第四段文字</p>
```

然后我們在第三個`<p>`上面,加上`position:relative`并且設置`left`和`top`值,看這個`<p>`有什么變化。
```
<p>第一段文字</p>
<p>第二段文字</p>
<p style="position:relative; top: 10px; left: 10px">第三段文字</p>
<p>第四段文字</p>
```

上圖中,大家應該要識別出兩個信息(相信大部分人會忽略第二個信息)
* 第三個`<p>`發生了位置變化,分別向右向下移動了10px;
* 其他的三個`<p>`位置沒有發生變化,這一點也很重要。
可見,**relative 會導致自身位置的相對變化,而不會影響其他元素的位置、大小**。這是 relative 的要點之一。還有第二個要點,就是 relative 產生一個新的定位上下文。下文有關于定位上下文的詳細介紹,這里可以先通過一個例子來展示一下區別:

注意看這兩圖的區別,下文將有解釋。
### absolute
還是先寫一個基本的 demo。
```
<p>第一段文字</p>
<p>第二段文字</p>
<p style="background: yellow">第三段文字</p>
<p>第四段文字</p>
```

然后,我們把第三個`<p>`改為`position:absolute;`,看看會發生什么變化。

從上面的結果中,我們能看出幾點信息:
* absolute 元素脫離了文檔結構。和 relative 不同,其他三個元素的位置重新排列了。只要元素會脫離文檔結構,它就會產生破壞性,導致父元素坍塌。(此時你應該能立刻想起來,float 元素也會脫離文檔結構。)
* absolute 元素具有“包裹性”。之前`<p>`的寬度是撐滿整個屏幕的,而此時`<p>`的寬度剛好是內容的寬度。
* absolute 元素具有“跟隨性”。雖然 absolute 元素脫離了文檔結構,但是它的位置并沒有發生變化,還是老老實實地呆在它原本的位置,因為我們此時沒有設置 top、left 的值。
* absolute 元素會懸浮在頁面上方,會遮擋住下方的頁面內容。
最后,通過給 absolute元素設置 top、left 值,可自定義其內容,這個都是平時比較常用的了。這里需要注意的是,設置了 top、left 值時,元素是相對于最近的定位上下文來定位的,而不是相對于瀏覽器定位。
### fixed
其實 fixed 和 absolute 是一樣的,唯一的區別在于:absolute 元素是根據最近的定位上下文確定位置,而 fixed 根據 window (或者 iframe)確定位置。
> 題目:relative、absolute 和 fixed 分別依據誰來定位?
### 定位上下文
relative 元素的定位永遠是相對于元素自身位置的,和其他元素沒關系,也不會影響其他元素。

fixed 元素的定位是相對于 window (或者 iframe)邊界的,和其他元素沒有關系。但是它具有破壞性,會導致其他元素位置的變化。

absolute 的定位相對于前兩者要復雜許多。如果為 absolute 設置了 top、left,瀏覽器會根據什么去確定它的縱向和橫向的偏移量呢?答案是瀏覽器會遞歸查找該元素的所有父元素,如果找到一個設置了`position:relative/absolute/fixed`的元素,就以該元素為基準定位,如果沒找到,就以瀏覽器邊界定位。如下兩個圖所示:


* * *
## `flex`布局
布局的傳統解決方案基于盒子模型,依賴 `display` 屬性 + `position` 屬性 + `float` 屬性。它對于那些特殊布局非常不方便,比如,垂直居中(下文會專門講解)就不容易實現。在目前主流的移動端頁面中,使用 flex 布局能更好地完成需求,因此 flex 布局的知識是必須要掌握的。
### 基本使用
任何一個容器都可以使用 flex 布局,代碼也很簡單。
```
<style type="text/css">
.container {
display: flex;
}
.item {
border: 1px solid #000;
flex: 1;
}
</style>
<div class="container">
<div class="item">aaa</div>
<div class="item" style="flex: 2">bbb</div>
<div class="item">ccc</div>
<div class="item">ddd</div>
</div>
```

注意,第三個`<div>`的`flex: 2`,其他的`<div>`的`flex: 1`,這樣第二個`<div>`的寬度就是其他的`<div>`的兩倍。
### 設計原理
設置了`display: flex`的元素,我們稱為“容器”(flex container),其所有的子節點我們稱為“成員”(flex item)。容器默認存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis)。主軸的開始位置(與邊框的交叉點)叫做 main start,結束位置叫做 main end;交叉軸的開始位置叫做 cross start,結束位置叫做cross end。項目默認沿主軸排列。單個項目占據的主軸空間叫做 main size,占據的交叉軸空間叫做 cross size。

將以上文字和圖片結合起來,再詳細看一遍,這樣就能理解 flex 的設計原理,才能更好地實際使用。
### 設置主軸的方向
`flex-direction`可決定主軸的方向,有四個可選值:
* row(默認值):主軸為水平方向,起點在左端。
* row-reverse:主軸為水平方向,起點在右端。
* column:主軸為垂直方向,起點在上沿。
* column-reverse:主軸為垂直方向,起點在下沿。
```
.box {
flex-direction: column-reverse| column | row | row-reverse;
}
```
以上代碼設置的主軸方向,將依次對應下圖:

### 設置主軸的對齊方式
`justify-content`屬性定義了項目在主軸上的對齊方式,值如下:
* flex-start(默認值):向主軸開始方向對齊。
* flex-end:向主軸結束方向對齊。
* center: 居中。
* space-between:兩端對齊,項目之間的間隔都相等。
* space-around:每個項目兩側的間隔相等。所以,項目之間的間隔比項目與邊框的間隔大一倍。
```
.box {
justify-content: flex-start | flex-end | center | space-between | space-around;
}
```

### 交叉軸的對齊方式
`align-items`屬性定義項目在交叉軸上如何對齊,值如下:
* flex-start:交叉軸的起點對齊。
* flex-end:交叉軸的終點對齊。
* center:交叉軸的中點對齊。
* baseline: 項目的第一行文字的基線對齊。
* stretch(默認值):如果項目未設置高度或設為 auto,將占滿整個容器的高度。
```
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}
```

* * *
## 如何實現居中對齊?
> 題目:如何實現水平居中?
### 水平居中
inline 元素用`text-align: center;`即可,如下:
```
.container {
text-align: center;
}
```
block 元素可使用`margin: auto;`,PC 時代的很多網站都這么搞。
```
.container {
text-align: center;
}
.item {
width: 1000px;
margin: auto;
}
```
絕對定位元素可結合`left`和`margin`實現,但是必須知道寬度。
```
.container {
position: relative;
width: 500px;
}
.item {
width: 300px;
height: 100px;
position: absolute;
left: 50%;
margin: -150px;
}
```
> 題目:如何實現垂直居中?
### 垂直居中
inline 元素可設置`line-height`的值等于`height`值,如單行文字垂直居中:
```
.container {
height: 50px;
line-height: 50px;
}
```
絕對定位元素,可結合`left`和`margin`實現,但是必須知道尺寸。
* 優點:兼容性好
* 缺點:需要提前知道尺寸
```
.container {
position: relative;
height: 200px;
}
.item {
width: 80px;
height: 40px;
position: absolute;
left: 50%;
top: 50%;
margin-top: -20px;
margin-left: -40px;
}
```
絕對定位可結合`transform`實現居中。
* 優點:不需要提前知道尺寸
* 缺點:兼容性不好
```
.container {
position: relative;
height: 200px;
}
.item {
width: 80px;
height: 40px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: blue;
}
```
絕對定位結合`margin: auto`,不需要提前知道尺寸,兼容性好。
```
.container {
position: relative;
height: 300px;
}
.item {
width: 100px;
height: 50px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
```
其他的解決方案還有,不過沒必要掌握太多,能說出上文的這幾個解決方案即可。
* * *
## 理解語義化
> 題目:如何理解 HTML 語義化?
所謂“語義”就是為了更易讀懂,這要分兩部分:
* 讓人(寫程序、讀程序)更易讀懂
* 讓機器(瀏覽器、搜索引擎)更易讀懂
### 讓人更易讀懂
對于人來說,代碼可讀性、語義化就是一個非常廣泛的概念了,例如定義 JS 變量的時候使用更易讀懂的名稱,定義 CSS class 的時候也一樣,例如`length` `list`等,而不是使用`a` `b`這種誰都看不懂的名稱。
不過我們平常考查的“語義化”并不會考查這么廣義、這么泛的問題,而是考查 HTML 的語義化,是為了更好地讓機器讀懂 HTML。
### 讓機器更易讀懂
HTML 符合 XML 標準,但又和 XML 不一樣 —— HTML 不允許像 XML 那樣自定義標簽名稱,HTML 有自己規定的標簽名稱。問題就在這里 —— HTML 為何要自己規定那么多標簽名稱呢,例如`p` `div` `h1` `ul`等 —— 就是為了語義化。其實,如果你精通 CSS 的話,你完全可以全部用`<div>`標簽來實現所有的網頁效果,其他的`p` `h1` `ul`等標簽可以一個都不用。但是我們不推薦這么做,這樣做就失去了 HTML 語義化的意義。
拿搜索引擎來說,爬蟲下載到我們網頁的 HTML 代碼,它如何更好地去理解網頁的內容呢?—— 就是根據 HTML 既定的標簽。`h1`標簽就代表是標題;`p`里面的就是段落詳細內容,權重肯定沒有標題高;`ul`里面就是列表;`strong`就是加粗的強調的內容 …… 如果我們不按照 HTML 語義化來寫,全部都用`<div>`標簽,那搜索引擎將很難理解我們網頁的內容。
為了加強 HTML 語義化,HTML5 標準中又增加了`header` `section` `article`等標簽。因此,書寫 HTML 時,語義化是非常重要的,否則 W3C 也沒必要辛辛苦苦制定出這些標準來。
* * *
## CSS3 動畫
CSS3 可以實現動畫,代替原來的 Flash 和 JavaScript 方案。
首先,使用`@keyframes`定義一個動畫,名稱為`testAnimation`,如下代碼,通過百分比來設置不同的 CSS 樣式,規定動畫的變化。所有的動畫變化都可以這么定義出來。
```
@keyframes testAnimation
{
0% {background: red; left:0; top:0;}
25% {background: yellow; left:200px; top:0;}
50% {background: blue; left:200px; top:200px;}
75% {background: green; left:0; top:200px;}
100% {background: red; left:0; top:0;}
}
```
然后,針對一個 CSS 選擇器來設置動畫,例如針對`div`元素設置動畫,如下:
```
div {
width: 100px;
height: 50px;
position: absolute;
animation-name: myfirst;
animation-duration: 5s;
}
```
`animation-name`對應到動畫名稱,`animation-duration`是動畫時長,還有其他屬性:
* `animation-timing-function`:規定動畫的速度曲線。默認是`ease`
* `animation-delay`:規定動畫何時開始。默認是 0
* `animation-iteration-count`:規定動畫被播放的次數。默認是 1
* `animation-direction`:規定動畫是否在下一周期逆向地播放。默認是`normal`
* `animation-play-state` :規定動畫是否正在運行或暫停。默認是`running`
* `animation-fill-mode`:規定動畫執行之前和之后如何給動畫的目標應用,默認是`none`,保留在最后一幀可以用`forwards`
> 題目:CSS 的`transition`和`animation`有何區別?
首先`transition`和`animation`都可以做動效,從語義上來理解,`transition`是過渡,由一個狀態過渡到另一個狀態,比如高度`100px`過渡到`200px`;而`animation`是動畫,即更專業做動效的,`animation`有幀的概念,可以設置關鍵幀`keyframe`,一個動畫可以由多個關鍵幀多個狀態過渡組成,另外`animation`也包含上面提到的多個屬性。
## 重繪和回流
重繪和回流是面試題經常考的題目,也是性能優化當中應該注意的點,下面筆者簡單介紹下。
* **重繪**:指的是當頁面中的元素不脫離文檔流,而簡單地進行樣式的變化,比如修改顏色、背景等,瀏覽器重新繪制樣式
* **回流**:指的是處于文檔流中 DOM 的尺寸大小、位置或者某些屬性發生變化時,導致瀏覽器重新渲染部分或全部文檔的情況
相比之下,**回流要比重繪消耗性能開支更大**。另外,一些屬性的讀取也會引起回流,比如讀取某個 DOM 的高度和寬度,或者使用`getComputedStyle`方法。在寫代碼的時候要避免回流和重繪。比如在筆試中可能會遇見下面的題目:
> 題目:找出下面代碼的優化點,并且優化它
```
var data = ['string1', 'string2', 'string3'];
for(var i = 0; i < data.length; i++){
var dom = document.getElementById('list');
dom.innerHTML += '<li>' + data[i] + '</li>';
}
```
上面的代碼在循環中每次都獲取`dom`,然后對其內部的 HTML 進行累加`li`,每次都會操作 DOM 結構,可以改成使用`documentFragment`或者先遍歷組成 HTML 的字符串,最后操作一次`innerHTML`。
* * *
## 小結
本小節總結了 CSS 和 HTML 常考的知識點,包括 CSS 中比較重要的定位、布局的知識,也介紹了一些 CSS3 的知識點概念和題目,以及 HTML 的語義化。