css佈局基礎總結

輝衛無敵發表於2018-07-20

前端css佈局知識繁雜,實現方式多種多樣。想寫出高效、合理的佈局,必須以深厚的css基礎為前提。為了方便記憶和複習,將css佈局要點記錄如下。內容較多,應用方面說的不太詳細,但都是很實用的點。

所謂佈局,就是指將元素設定為我們想要的大小,放置於我們想要的位置,位置尺寸是核心兩要素。這些元素其實就是一些方塊,頁面就是由各種方塊拼湊而成。現在佈局方式主要分為三種:

  • 傳統css佈局方案(position,float,line-height等配合)。實現複雜,需要多種屬性配合使用,相容性最好。
  • flex佈局方案。彈性佈局,實現方便,相容性較好。
  • gird佈局方案。

使用哪種佈局方式,看專案具體要求,如果不需要相容IE,建議使用flex或者grid,這兩種是未來趨勢。如果要考慮相容,則最好使用傳統css佈局方案。

傳統css佈局方案

css標準盒模型

前面提過,一個html元素,就是一個方塊,講究一點的話就是盒模型。盒模型長這樣:

盒模型

我們可以在chrome開發者工具的styles中檢視:

盒模型

css盒模型,分為4部分:

  • content:顯示元素內容區域,包含子孫元素的地方
  • padding:內容區域到邊框的距離,也稱內邊距
  • border:顯示自身輪廓
  • margin: 用於設定元素自身和同級元素或者父級元素的距離

規則

無規矩不成方圓。所有的html元素都是按照一定規則去排列渲染的。在html中,不只有一種規則,是多種規則的混合。瞭解這些渲染規則和這些規則生效的條件對於我們理解css有很大幫助。

文件流

文件流可以理解為所有元素的預設渲染規則。它的規則很簡單:元素按照自己的型別的佈局特性從左到右,從上往下依次排列。

元素在佈局上分為三種型別:

  • 塊級元素:預設佔據一整行,即時設定寬度也還是佔據一整行,由margin來補充(霸道總裁),有block、table、flex、grid、list-item
  • 內聯元素:寬度由內容撐開,只佔據自己的位置,即時設定了寬度也不起作用,margin也是左右起作用,上下不起作用(大家閨秀),有inline
  • 內聯塊級元素: 寬度由內容撐開,只佔據自己的位置,可以設定自己的寬度(兩個人的孩子?),有inline-block、inline-table、inline-flex、inline-grid

上面內聯元素其實說的不太正確,這裡需要特殊說一下,內聯元素又分為兩種:

  • 可置換內聯元素:該元素展示的內容不在css作用域範圍之內,典型比如img、video、object、input、textarea等,展示內容由src、value等決定,這種元素可以設定寬高
  • 不可置換內聯元素:普通的內聯元素,比如span、a、i、em等

有兩種方式可以脫離文件流,絕對定位和浮動,我們下面再詳細說。

BFC

BFC全稱是Block Formatting Context,塊級格式化上下文。在BFC環境中的元素按照如下規則渲染:

  • 和文件流一樣,元素按照自己型別的佈局特性從左到右,從上往下依次排列。
  • BFC是一個獨立、封閉的渲染區域。子元素的樣式對BFC外部產生影響。
  • BFC可以識別浮動子元素。
  • BFC可以識別浮動的同級元素。利用這一點可以製作彈性佈局。

那麼什麼樣的渲染區域是一個BFC哪?下列幾種方式可以顯示宣告一個BFC的渲染區域:

  • 根元素或包含根元素的元素
  • float的值不為none
  • position為absolute或者fixed
  • overflow的值不為visible
  • display為inline-block、table-cell、table-caption

尺寸

元素的尺寸有兩種情況:

  • 預設情況: 塊級元素寬度預設為100%,高度由內容撐開;內聯元素和內聯塊級元素寬度和高度預設由內容撐開。
  • 開發者設定: 主動設定width、height、line-height等

我們先來明確一下元素的尺寸概念,我們都知道元素的盒模型,元素由content、padding、border、margin四個區域組成。那麼尺寸具體是指什麼?我們可以把尺寸分為三類:

  • 元素尺寸:由content、padding、border組成,在原生的DOM API中用offsetWidth、offsetHeight獲取
  • 元素內部尺寸: 由content、padding組成,在原生的DOM API中用clientWidth和clientHeight獲取
  • 元素外部尺寸: 由content、padding、border、margin組成,沒有對應的DOM API,但是理解這個,對佈局很有幫助

而我們在css中設定的width、height代表哪部分割槽域是有歧義的,我們通過設定box-sizing,可以切換width、height所代表的區域。

box-sizing

box-sizing,可以切換width、height所代表的區域。box-sizing主流瀏覽器有兩個值:

  • content-box: width、height設定的是content的寬高
  • border-box: width、height設定的是content、padding、border加起來的寬高

box-sizing的這兩個屬性,造成width、height的二義性,而且很多情況下是全域性設定的,隱蔽性很強,對於新手來說很容易懵。這兩個屬性的產生有一定的歷史原因,最開始的盒模型預設採用的border-box,早期的IE就是這種,也叫IE盒模型。後來W3C覺得content-box更好,又把盒模型預設改為content-box模式。後來的後來,隨著彈性佈局的流行,border-box的優勢越來越明顯,大家都更願意使用border-box來佈局,W3C又想把border-box搞回來。但是已經有好多基於content-box的網站,為了相容性也不能隨便改。於是,W3C就想出了box-sizing這種方式,支援了border-box,但是預設還是content-box。使用box-sizing應注意的幾點:

  • 加入要全域性設定box-sizing: border-box,要注意對第三方元件庫的侵入,因為第三方元件很可能是基於content-box來佈局的
  • 設定所有元素的box-sizing屬性為inherit:
    *, *:before, *:after {
        box-sizing: inherit;
    }
    複製程式碼
    這樣我們可以設定父元素的box-sizing,就可以控制所有子元素的box-sizing屬性,對於我們封裝元件,設定整個元件環境很方便,只有我們在封裝元件時強設定box-sizing,就不怕全域性的樣式侵入了。
  • content-box模式在設定彈性佈局時,可以使用calc屬性輔助實現,或者弄兩層div實現

尺寸的百分比設定

包含塊

我們知道width, height都是可以設定百分比,那這個百分比的參照物是誰?這裡引出一個概念,叫做包含塊(CB, Contanining Block),一個元素的包含塊就是該元素的width、height百分比的參照物。

很多新手同學,在設定寬高百分比的時候,有時候覺得莫名其妙,各種奇怪現象,怎麼設定都不起作用。其實就是沒有包含塊的概念。一個元素的包含塊是誰,主要取決於該元素的position屬性,總結如下:

  • position為static和relative的元素,包含塊為其父元素的content-box
  • position為absolute的元素,包含塊為其最近的定位非static的祖先元素的padding-box,如果沒有定位非static的祖先元素,則為初始包含塊(後面解釋)
  • position為fixed的元素,包含塊為視口viewport
  • position為absolute和fixed時,包含塊也可能是由滿足以下條件的最近父級元素的padding-box:
    1. A transform or perspective value other than none
    2. A will-change value of transform or perspective
    3. A filter value other than none or a will-change value of filter (only works on Firefox) 這條比較特殊,遇到的情況比較少,單獨拿出來

除了width, height的百分比相對於包含塊設定外,margin、padding的百分比也是相對於包含塊設定,只不過margin、padding百分比相對於包括塊的寬度設定(水平模式下)。絕對定位的偏移屬性top、left、right、bottom也是相對於包含塊設定,後面再詳細說。

初始包含塊

元素的包含塊都是自己的祖先元素,那hmtl元素沒有祖先元素,它的百分比設定相對於誰那?就是初始包含塊,根元素(hmtl)所在的包含塊是一個被稱為初始包含塊的矩形。這個矩形的大小就是瀏覽器視口的大小。有一點需要注意,那些沒有定位非static祖先元素的參照物是初始包含塊,而非html元素。

margin

因為margin在佈局中有一些重要特性和特殊情況,所以單獨拿出來講一下。

margin:auto

我們知道,塊級元素即使設定了寬度,也會佔滿一行,為什麼會這樣?剩餘的空間被誰佔了?

這裡要明確一點,塊級元素佔據一行,是指塊級元素的外部尺寸佔據一行,就是margin-box。當margin設定為auto的時候,margin會自動佔滿剩餘空間。

  • margin-left: 預設為0,為auto時,自動充滿剩餘空間
  • margin-right: 預設為0,為auto時,自動充滿剩餘空間
  • margin-top: 預設為0,為auto時,值還是為0
  • margin-bottom: 預設為0,為auto時,值還是為0

當margin-left和margin-right同時為auto,就會平分剩餘空間,這就是margin:auto會使元素水平居中的原因。然後margin: auto卻不能使元素垂直居中,這是因為在垂直方向上,塊級元素不會自動擴充,它的外部尺寸沒有自動充滿父元素,也沒有剩餘空間可說。如果我們在父元素上設定writing-mode: vertical-lr,這時margin:auto就會使子元素垂直居中,而水平居中無效。

那有沒有什麼辦法使用margin:auto讓元素同時水平垂直居中?答案是有的,就是絕對定位的情況下。

.father {
    width: 300px; height:150px;
    position: relative;
}
.son {
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    width: 200px;
    height: 200px;
    margin: auto;
}
複製程式碼

此時的son的外部尺寸,就會自動充滿它的包含塊,效果和塊級元素類似。這時候設定.son元素的寬高,就會有剩餘空間出來,margin:auto會自動平分剩餘空間,使.son水平垂直居中。

margin對佈局影響

margin對元素的影響有2個:

  • 影響內部尺寸
  • 影響外部尺寸
影響元素內部尺寸

塊級元素這種寬度方向自動充滿空間的佈局特性,當沒有設定寬度,設定margin-left和margin-right為對元素的內部空間有影響。margin為正值時,元素尺寸縮小;margin為負值時,元素尺寸增大。因為元素的外部尺寸大小已經定了,就是其包含塊的尺寸,而外部尺寸等於元素尺寸加上margin,如果margin為負,元素尺寸自然就增大了。利用內部尺寸增大、外部尺寸不變這個特點,我們可以進行等間隔列表的佈局。比如我們實現一個一行三列,兩側無間隙,中間間隙為20px的佈局:

.father{
    margin-right: -20px;
}
.son{
    width: calc( calc(100% - 20px * 3) / 3);
    margin-right: 20px;
}
複製程式碼
影響元素外部尺寸

在元素寬高固定的時候,相當於元素尺寸固定了,margin開始影響元素的外部尺寸。正margin會使元素元素的外部尺寸增大,負margin會使元素元素的外部尺寸縮小。正值很好理解,就不說了。主要說一下負值,這時候margin-top、margin-left和margin-right、margin-bottom的表現看起來是有區別的,實質是一樣。margin-top、margin-left為負值時,表現為元素向上或者向左移動,margin-right、margin-bottom為負值時,表現為其右側元素或者下面元素都會相應地向左或者向上移動。仔細想想,這都是元素外部尺寸縮小的表現。margin-top、margin-left使左側上上側尺寸縮小,就會自然向左或者向上移動。margin-right、margin-bottom使右側和下側尺寸縮小,旁邊和下面的元素自然就會往左或者往上佔位。

margin合併

塊級元素的上邊距和下邊距在某些場景下會發生合併行為。需要注意的是浮動元素和絕對定位元素不會發生合併,且合併只發生在垂直方向。有以下3中合併場景

  • 相鄰兄弟元素
  • 父元素和第一個/最後一個元素
  • 元素內容為空時,height為0,自己的上邊距和下邊距會發生合併

margin合併的計算規則

  • 正正取大
  • 正負相加
  • 負父取小(-20px, -50px合併取-50px)

阻止margin合併的方法

  • 把合併元素變成BFC,比如設定display: inline-block,overflow: hidden, float: left等
  • 父子合併/空元素合併時,給父元素/空元素設定border、padding都能阻止合併

內聯元素尺寸

主要想說line-height,vertical-align兩個屬性,這兩個屬性對高度有重要的影響,而且如果沒有理解這兩個屬性的作用,會經常碰到一些不好解決的詭異的佈局問題。

幽靈空白節點

這個名字是張鑫旭大神定義的,在W3C規範中也是可以找到根據的。在《CSS世界》這本書中,為了證明幽靈空白節點的存在,舉了一個例子:

div {
    background-color: #cd0000;
}
span {
    display: inline-block;
}
<div><span></span></div>
複製程式碼

你可能猜到了,div的高度不為0,我在chrome下試了試高度為21px,這個高度猜測應該和div的字型、字型大小、行高有關。這個幽靈空白節點的存在,會引起一些怪異現象,比如:

<div><img></img></div>
複製程式碼

div的高度總是比img高一些,img下總有一個間距。這也是一個幽靈空白節點在起作用。

line-height

line-height翻譯過來就是行高。它指的是指行框的高。那什麼是行框那?

行框是由內聯元素或者內聯塊級元素組成的一行。通過嘗試,發現一行形成行框的兩個前提是,要麼有文字內容,要麼有內聯塊級元素,如果其中是一堆空的內聯元素,比如span,是沒有形成行框的。

line-height就是指定行框佔據的高度。只有形成了行框,line-height才會起作用。

這裡可能有一些容易誤解的情況,比如:

.father{
    line-height: 300px;
}
<div class="father">
    <div class="son">
        你好
    </div>
</div>
複製程式碼

這時候.fahter高度是300px,這裡注意的是,不是line-height對.son起作用了,是.son內部形成了一個行框,line-height對這個行框起作用了,.son的高度是300px,撐開了.father。

vertical-align

vertical-align用來設定內聯元素或者內聯塊級元素在行框內的對齊方式或者說垂直位置。verticle-align有四類值,我們在這裡直說其中對佈局比較重要的兩類:

  • 線類: baseline(預設值)、top、middle、bottom
  • 數值類:比如20px、20em
基線

先來看一張圖:

基線

vertical-align預設是基線對齊的,基線的位置很重要。而基線的定義是:字母x的下邊緣。中線的位置是基線往上 1/2 x-height 高度,就是x交叉的地方。這裡有一個需要注意的地方是,純文字的中線是x交叉的地方,但是內聯元素的中線卻是內聯元素的正中間位置。我們來看一個例子:

div{
  line-height: 100px;
  background: yellow;
}
<div>
  <span class="world">xhello</span>
</div>
複製程式碼

效果如下:

正常高度

一切正常,div高度100px,文字也是近似垂直居中。但是如果我們在.world元素上加vertical-align: middle,我們會發現文字向下偏移,且div高度變成了102px,效果如下:

不正常高度

這是怎麼回事那,vertical-align設定的到底跟誰對齊?答案是幽靈空白節點,幽靈空白節點是個很神奇的存在,看不見,但卻實實在在地在起作用,它的作用就相當於一個x字元的作用,用來提供對齊基準。我們上面說到的,div元素中有一個圖片的例子,img是可替換內聯元素,它的baseline就是它的底部,預設是baseline對齊,所以圖片對齊的是幽靈空白節點x的基線,x基線位置往下是一段空白的,這就是這段空白的來源。為了更直觀,我們在上面這個例子的span元素前面加一個x字元,當做幽靈空白節點,看一下效果:

不正常高度

本來,xxhello中的x是對齊的。設定vertical-align之後,我們可以看到xhello字元向下挪了一段位置,這是因為span元素的垂直中線位置,比xhellox交叉點要高,現在要和前面的x交叉點對齊,就往下移動了。又因為xxhello的行高都是100px,但是現在因為由於錯位,造成整體高度多了2px。

垂直居中應用

利用line-height和vertical-align可以設定多種場景下的垂直居中。

  • 單行居中
div{
    line-height: 任意值
}
<div>hello world</div>
複製程式碼
  • 父元素定高垂直居中,這個在.box上設定line-height實際上設定了幽靈空白節點的行高是120px,然後.content和幽靈空白節點middle對齊就實現了垂直居中。
.box {
    line-height: 120px;
    background-color: #f0f3f9;
}
.content {
    display: inline-block;
    line-height: 20px;
    margin: 0 20px;
    vertical-align: middle;
}
<div class="box">
    <div class="content">多行文字...</div>
</div>
複製程式碼
  • 父元素不定高垂直居中,原理其實和父元素定高情況相同,但是因為不定高,你沒法設定父元素的line-height,這時候我們可以設定一個父元素的偽元素,設定成高度100%,vertical:middle,因為元素是從左上向下排列,所以vertical:middle會把幽靈空白節點的x拉到父元素垂直中心位置。這時候,我們再設定子元素的vertical-align:middle就達到了目的。
.container {
    position: fixed;
    top: 0; right: 0; bottom: 0; left: 0;
    background-color: rgba(0,0,0,.5);
}
.container:after {
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
}
.dialog {
    display: inline-block;
    vertical-align: middle;
}
<div class="container">
    <div class="dialog"></dialog>
</div>
複製程式碼

位置

元素的位置由position屬性和float屬性決定。

position屬性

position有4個值:

  • static: 預設值,按照css規則排列
  • relative:相對定位,可設定偏移量,top,left,right,bottom,偏移量百分比大小的參照物是其包含塊,偏移量的位置參照物是自身位置
  • absolute:絕對定位,可設定偏移量,top,left,right,bottom,偏移量百分比大小和位置的參照物是其包含塊
  • fixed:固定定位,可設定偏移量,top,left,right,bottom,偏移量百分比大小和位置的參照物是其包含塊viewport

需要注意的地方有:

  • position為relative、absolute、fixed時,如果不設定偏移量,預設位置為文件流的正常位置
  • position為absolute、fiexed的元素,脫離文件流,文件流中的元素不識別該元素,不識別它的佔位和寬高度(不是自家孩子)
  • position為absolute、fiexed的元素,寬度的渲染規則和內聯塊級元素相同,寬度預設為由內容撐開,可設定寬高

float屬性

float屬性可以使元素脫離文件流,向左或者向右移動,直至碰到包含塊的padding或者碰到另一個浮動元素。注意:

  • position為absolute、fixed會讓浮動屬性失效
  • float會使元素塊狀化,dispaly的值,inline-table變為table,剩下的都變為blcok,但是元素寬度的渲染規則和內聯塊級元素相同,寬度預設為由內容撐開,可設定寬高

float最著名的就是它的高度坍塌,之所以有高度坍塌,和float設計的初衷有關。float屬性最初是設計用來實現文字環繞效果的。

<div class="img-wrap">
    <img style="float: left"/>
</div>
<p>我是文字,我要環繞你</p>
複製程式碼

.img-wrap因為高度塌陷,實際上不識別float元素,高度為0,p元素與其重合,從而實現文字環繞效果。

高度塌陷特性在實現佈局的時候是不合理的,而現在文字環繞的場景很少,float基本都用來佈局使用了。這現在實際上已經成為了一個“bug”的存在。所以css又提供了一個屬性用來清除浮動:clearfix。

clearfix

clearfix在mdn上的解釋為指定一個元素是否可以在它之前的浮動元素旁邊(我覺得這裡用重合更貼切),或者必須向下移動(清除浮動) 在它的下面。

注意上面這句話,指定一個元素表示clearfix的作用目標是元素自身,在它之前的浮動元素表示clearfix的作用條件是它前面有浮動元素,而clearfix的作用效果是讓元素換行顯示。所以clearfix的作用是,如果元素前面有浮動元素,可以指定該元素換行顯示,或者說是識別前面的浮動元素。clearfix有3個值:

  • left: 清除左浮動
  • right: 清除右浮動
  • both: 清除左右浮動

一般情況下采用clearfix: both。清除浮動一個重要應用就是解決float高度塌陷的問題。通用的解決方案是設定.clearfix類:

.clearfix {
    display: block;
    zoom: 1;
    &:after {
        content: "";
        display: block;
        font-size: 0;
        height: 0;
        clear: both;
        visibility: hidden;
    }
}
複製程式碼

在包含浮動元素的父元素上設定該類即可。

部落格文章地址

參考

  • 張鑫旭 《CSS世界》
  • https://mp.weixin.qq.com/s/8eAfz_I5xIhh7oFRifxaFw

相關文章