徹底搞定vertical-align垂直居中不起作用疑難雜症[轉]

轉自:https://www.colabug.com/2345532.html

原本我的文章標題是 深入探討line-height與vertical-align的疑難雜症 ,但這樣似乎沒能針對性的拋出一個問題,故改成“ 徹底搞定vertical-align垂直居中不起作用疑難雜症 ”。因此,本文講解的還是 line-height 和 vertical-align 。至於各種垂直居中的方法,網上包括掘金裏已有不少文章介紹,本文僅講解針對 vertical-align 對齊的方式。

在實際項目中, line-height 和 vertical-align 是使用頻率非常高的兩個CSS屬性。其中 line-height用於指定文字的行高, vertical-align 用於指定元素的垂直方向對其方式。但是,我們常常在應用兩個屬性的過程中,遇到許多預想不到的結果,比如使用vertical不能實現垂直居中(vertical-align無效這個問題是高頻提問的問題)。這兩個屬性關係非常密切,隨時隨地都存在它們共同作用的結果,要使用好這兩個屬性,只有深入的理解了他們的作用機理,才能解決實際使用過程中遇到的種種疑惑。

一、 幾個概念

  1. 行高 : 兩行文字基線之間的距離。
  2. 基線 : 英語字母中的概念,是指書寫英語字母時,字母x底部所在的位置,即我們用英語字母本子書寫時從上往下,格子中的倒數第二條線。在CSS中值爲baseline。以下圖爲行高,基線,以及 vertical-align 屬性中, baseline , bottom , middle , top 幾個值的關係。

糾正:上圖中的middle描述有錯,正確應該爲baseline往上二分之一x-height所在的位置,其中x-height指的是字母x的高度,關於x-height,具體你可以點擊這裏瞭解

  1. 四種內聯盒子

    在CSS中有四種內聯盒子,分別是 containing box , inline boxes , line box , content area 。分別如下:

(1) containing box : 外層盒子模型,包含了其他的boxes

(2) line boxes : 由一個一個的inline boxes組成,一行即爲一個line box,單個line box的高度由其包含的所有inline boxes中,高度最大的那個決定(由 line-height 起作用,後面解釋),而一個一個的line box的高度就堆疊成了containing box的高度。

(3) inline boxes : 不會成塊顯示,而是並排顯示在一行的boxes,如 span , a , em 等標籤以及匿名inline boxes(即不含把標籤的裸露的文字)。

(4) content area : 圍繞文字看不見的box,其大小與font-size有關,其高度可以認爲鼠標選中文字時背景色的高度(後面解釋)。 以下爲幾種boxes的關係(圖片中,粉紅色爲鼠標選中的文字部分)


 

這裏是一個div,裏面包含了獨立的文字, span標籤 em標籤, 以及其他的一些文字。

理解四種box非常重要,平時的使用浮動,定位,父級高度自動撐開等表現都是與boxes的作用有很大的關係。

二、深入理解行高line-height

  1. line-height 與 line boxes的高度關係

    現在不少人認爲,line boxes的高度是由內部文字撐開的,但是實際上並不是文字撐開,而是由 line-height 來決定的。通過以下的比較,我們可以證明這個結論。


 

我是一行文字大小爲100px,但是line-height爲0的文字

我是一行文字大小爲0,但是line-height爲100px的文字

我是一行文字大小和line-height都爲100px的文字

.div {
  background: #f0f0f0;
  border:  1px solid #e0e0e0;
  margin: 10px;
}
.div1 {
  font-size: 16px;
  line-height: 0;
}
.div2 {
  font-size: 0;
  line-height: 100px;
}
.div3{
  font-size: 16px;
  line-height: 100px;
}
結果表現爲:

結果顯而易見,當文字的行高爲0時,就算它的字號大小很大,line box的高度爲0。相反,即使字體大小爲0,但如果行高不會0,則會撐開標籤的高度。同時,我們發現,行高還有一個特性就是垂直居中性,無論line boxes的高度是多少,其裏面的文字都是公用一條垂直居中線,利用這一個特性,我們還可以實現一些近似的垂直居中效果。(爲什麼近似,後面解釋)。

  1. 文本間距

    兩行文本之間的間距爲文本間距,在CSS中,文本上下的間距均爲文本間距的一半,即通過設置行高後,行高與字體大小差值將等分於文本上下。設文本頂部和底部的間距爲x,行高爲y,字體大小爲z,則滿足: 2x + z = y .也就是 x = (y - z) / 2 .這就是爲什麼有時候我們在設置樣式的時候文本的大小大於行高,導致多行文本之間會重疊在一起的現象。

  2. line-height值爲 1.5 , 150% 和 1.5em 的區別

    我們在給line-height賦值時,經常設置類似 1.5 , 1.5em , 150% 的值。但是弄懂幾個屬性值的區別非常重要。line-height屬性值具有繼承性,即上級設置的屬性值會在子級中繼承。幾個屬性的區別在於1.5是在子級繼承後會重新根據自身的字體大小重新計算行高值,而1.5em和150%則會在父級計算完行高值後,原封不動的作用於子級元素。看以下例子,可以形象的說明這一特點。

上圖可以看到,當父級元素爲的字體爲16px,行高爲150%`(即16x150%=24px)`時,最終行高24px將會被子元素繼承,由於子元素的字體大小爲40px,行高小於字體大小,根據上面第2點的公式我們可以得到,兩行文字之間的距離爲`2x = 24 - 40 = -16px`,所以兩行文字重疊在了一起;而當line-height爲1.5時,由於子元素會根據自身字體的大小重新計算行高,即`2x = (40 * 1.5) - 40 = 20px`,得到兩行文字之間的距離爲20px,文本將不會重疊在一起。1.5em與150%同理。
因此,我們在實際使用的過程中,絕大部分場景應該儘量的少用em和百分比值作爲line-height的值,而使用1.5,2等不帶單位的值替代。

三、深入理解vertical-align

初學者使用 vertical-align 屬性時,經常會發現最終的表現結果並不能如願,“vertical-align無效”也是CSS問題裏搜索頻率比較高的一個。大部分是因爲對於該屬性理解不夠透徹引起的,只有理解了該屬性的特點,表現行爲以及與其他屬性( 如line-height )的共同作用機制和效果,才能很好的解決vertical-align帶來的一些問題,並有效的利用它。

  1. 起作用的前提

    vertical-align起作用的前提是元素爲inline水平元素或table-cell元素,包括 span , img , input , button , td 以及通過display改變了顯示水平爲inline水平或者table-cell的元素。這也意味着,默認情況下, div , p 等元素設置vertical-align無效。

  2. 取值

    vertical-align可以有以下取值方式:

    (1)關鍵字:如 top , middle , baseline(默認值) , bottom , super , sub , text-bottom , text-top

    (2)長度值:如10px,-10px(均爲相對於baseline偏移)

    (3)百分比值:如10%,根據 line-height 作爲基數進行計算( 重要 )後的值。

  3. 各種屬性設置後的表現形式

    平時,我們用得最多的應該是 top , bottom , middle , baseline 等幾個關鍵字。而長度值等用得比較少。以下通過例子展示一下各個值的表現形式,並做解析。

以上所有外層標籤的行高爲50px,背景爲灰色,所有的對齊方式爲對圖片進行設置,紅色背景文字爲圖片的兄弟標籤span,完整源碼請看這裏。通過觀察我們可以看到,各個屬性值的表現如下:

(1) baseline: 默認的對齊方式,基線對齊,與父元素的基線對齊;

(2) top: 與行中的最高元素的頂端對齊,一般是父級元素的最頂端對齊;

(3) middle: 與父元素中線對齊(近似垂直居中);

(4) bottom: 與 top 相反,與父級元素的最低端對齊 ;

(5) text-top: 與父級元素 content area 的頂端對齊,不受行高以及周邊其他元素的影響。(如圖,由於行高爲50,但爲了保證與 content area 頂部對齊,故圖片上面有空隙,可以與 top 進行對比);

(6) text-bottom: 與 text-top 相反,始終與父級元素 content area 的低端對齊。同理可以與 bottom進行對比區分。注意,從圖中可以看到,貌似該值的表現行爲與 baseline 一致,但仔細觀察,可以看到,實際上 text-bottom 所在的線會比 baseline 低一點。

(7) 數值與百分比: 當數值爲正值時,對齊方式將以基線爲基準,往上偏移響應的大小,當爲負值時,往下偏移。而百分比則是根據行高的大小,計算出響應的數值,再以數值表現的方式進行偏移。(百分比根據行高計算偏移值這一點很重要,對後面講解的一些奇怪的現象可以做出解釋並找到解決的辦法)。

(8) super與sub: 這兩個值均是找到合適的基線作爲對齊的基線進行對齊,類似 super 標籤和 sub 標籤,但不縮放字體大小。

四、line-height與vertical-align的密切關係與應用

咋一看,我們很難看到這兩個屬性之間的關係,但是實際上,這兩個屬性的關係非常密切,而且無處不在。引用張鑫旭的話講就是“令人髮指的斷背基友關係”。我們在處理內聯元素的對齊,排列等,很多令人捉摸不透的奇怪現象都和vertical-align和line-height之間有很大的關係,基本上都能從它們身上找到原因和解決辦法。

  1. 幾個定義

    在講兩個屬性之間的關係和應用之前,先來了解幾個定義。

(1)inline-block基線:在CSS2可視化格式模型文檔中,指出了inline-block的基線是正常流中最後一個line box的基線,但是,如果這個line box裏面沒有inline boxes或者其overflow屬性值不是visible,那麼其基線就是margin bottom的邊緣。什麼意思呢?我們用一張圖片說明一下:

**糾正:圖片上面描述文字中的“紅色線爲設置了middle”改爲“黃色線爲設置了middle”**

(2)middle對齊:指元素的垂直中心線與父級基線往上二分之一X所在的位置的線對齊。有點繞,看下圖:

(3)文字下沉特性:文字是具有下沉特性的,就是文字的垂直中心點在文字所在區域的中線往下沉一點,不同字體的文字下沉的幅度不同,同時,文字大小越大,下沉越明顯。如上圖,X的中線點相對白色中線往下沉。

  1. 幾個現象

    我們先通過例子來看看幾個現象。


 

我有內容

.wrap {
  background: #249ff1;
}
span {
  display: inline-block;
  width: 100px;
  height: 100px;
  border: 1px solid #f00;
}
img {
  width: 100px;
}
.middle {
  vertical-align: middle;
}

以上代碼最後結果如下:

我們發現,放在div裏面的圖片,在底部會多出一點空白間隙,而第二個例子兩個樣式一樣的span,一個有文字一個沒有文字,我們的意願是想讓兩個span並排顯示,但結果錯位十分嚴重。至於第三個例子,圖片設置了居中對齊,但似乎沒有生效。那究竟空白間隙從何而來?爲什麼兩個span會錯位?圖片確實是沒有居中對齊嗎?要解釋清楚這個現象,必須弄清楚vertical-align和line-height之間的關係。我們從第一個例子開始,一步一步的分析。

首先,通過前面兩個屬性的表現特點分析我們知道,元素默認情況下的對齊方式是基線對齊,即baseline。而在瀏覽器中,都有默認的字體的大小,這個空隙就是來源於這兩個。爲了便於我們觀察,我們把wrap的行高設置爲一個相對大的值,在這裏我們設置爲50px。設置後表現如下:

可以看到,此時圖片底部的空白間隙變得更大。實際上,在wrap裏面雖然只有一個img標籤,但其實存在一個我們看不見的空白節點。這個特殊的空白節點與普通文節點一樣,具有文字大小,行高。因此,我們可以利用普通文本來代替這個節點來觀察現象。我們在圖片的後面輸入一串XXX。如下:

接着,我們再用一個inline-block化的span標籤將文字包裹並設置文字背景色。如下:

經過以上兩步改變,我們發現,圖片的位置沒有發生任何的變化,底部的間隙大小也沒有變化。此時,已經足以解釋爲什麼只有一個img標籤的情況下,圖片底部會出現間隙:由於空白節點的存在,圖片後面相當於跟了一個文本節點。而默認情況下,圖片的對齊方式是以父級元素的基線對齊,爲了保持與基線對齊,圖片底部必須留出間隙,大小爲上面提到的半倍文本間距[即( line-height – font-size ) / 2]。而且line-height的值越大,間隙將越大。到此爲止,結合我們上面對屬性值特點的一些分析,要找到問題的解決辦法就變得相當簡單了。要去除圖片底部的間隙,只需要將 line-height 和 vetical-align 屬性的其中一個給幹掉就可以了。可以有以下幾種方法:

(1)將圖片設置爲display:block(利用vertical-align的生效前提);

(2)將vertical-align設置爲top,bottom,或者middle等值(利用屬性值的表現行爲);

(3)將line-height設置爲0(利用line-height爲0時,基線上移);

(4)將font-size設置爲0 (如果line-height的值爲相對值,如1.5);

(5)將img設置浮動或者絕對定位(如果佈局允許的話)

現在,利用第二種方法,將vertical-align設置我bottom,設置後結果如下:

第二個例子,由前面的對於inline-block基線的定義可知,對於有內容的inline-block,其基線爲最後一行文本基線所在的位置,而對於空白的inline-block,其基線爲margin bottom邊緣所在位置,即底部邊緣。因爲默認情況下爲基線對齊,這兩條基線對齊後就形成了上圖那種錯位的現象。知道了錯位的原因,要解決也方便了。我們僅需將對齊方式設置爲bottom,middle,top等值就可以了。現在設置爲middle。效果如下:

至於第三個例子,有點讓人摸不着頭腦,這也是vertical-align無效被提問的最多的一種現象。按照vertical-align生效個條件可知,給img設置middle對齊後理論上應該是居中對齊纔對,但爲什麼沒有起作用呢?是真的沒有起作用嗎?答案是:起作用了。實際上,vertical-align:middle是起作用的了,但至於最後圖片爲什麼沒有在父級裏面垂直居中,是因爲後面的空白節點高度不足,導致基線偏上。按照中線的定義,中線也是偏上。我們可以用一個字母x代替後面的空白節點,來觀察現象。

從圖中可以看到,實際上圖片與文字確實是垂直居中對齊了。我們給父級的行高設置爲父級的高度,從而使基線往下偏移。效果如下:

此時,我們可以看到,圖片“近似”垂直居中在了父級元素。這是因爲設置行高後,根據之前分析的 line-height 等於 font-size + 2倍的文字上下間距 可知,父級基線往下。中線爲基線往上二分之一x高度,此時圖片的中線就與後面的x中線點對齊,實現了近似垂直居中的效果。

  1. 應用

    利用空白節點這個特性,以及行高和vertical-align的關係,我們可以做一些實際的應用。

    (1) 實現垂直居中

    由於空白節點存在,當我們給外層標籤設置一個較高的行高值時,由於行高的上下間距平分的性質,可以實現近似垂直居中的效果。此時,也不需要給父級標籤設置具體的高度值(因爲line-height會撐開父級)。


 

.wrap {
  background: #249ff1;
  margin: 10px;
}
.wrap1 {
  line-height: 200px;
  text-align: center;
}
.middle {
  vertical-align:  middle;
}

(2) 任意父級高度的垂直居中

在上面的提到的三種現象中,第三個例子,我們給父級設置line-height的值等於height的值,實現了近似垂直居中的效果。那如果父級的高度是隨着內容的變化而變化的怎麼辦?此時無法給父級設置一個特定的值,也不能使用百分比,因爲line-height是根據字體的大小來計算的。換個角度想,空白節點我們看不見,但是如果可以給它設置一個高度,讓它與父級高度一致,就解決了這個問題。怎麼給高度呢?答案是藉助輔助元素,我們可以在父級最後面增加一個inline-block化的span標籤,高度爲100%,font-size爲0,接着讓居中的元素居中對齊即可。


 

當然,這個span標籤也可以通過僞元素的來實現

(3) 實現多圖列表的兩端對齊

在做類似商品列表的佈局時,我們時常需要每一行列表的實現兩端對齊。實現的方法有很多,這裏我們用 display:inline-block + 輔助元素 來實現。如下:


 
li{
  list-style-type: none;
  border:  1px solid red;
  display: inline-block;
  width: 28%;
  text-align: center;
}
.fix {
  display: inline-block;
  width: 28%;
  text-align: center;
}
ul {
  width:  720px;
  border:  1px solid #a7a7a7;
  text-align: justify;
  margin: 20px auto;
}
li img {
  width: 95%;
}

結果如下:

每個圖片的下方會有一個空白,前面已經解釋過空白的來源,我們給ul設置line-height:0即可解決。效果如下:

去除了空白後,發現在最後一個圖片的底部也有一個相對較大的空白,爲什麼呢?來源於哪裏?爲了方便觀察,我們給每一個span.fix增加一個邊框,並在最後一個span.fix增加幾個字母。結果如下:

從結果可以知道,最後一個span與文字節點的基線對齊。還記得前面說過的兩個inline-block排列錯位的例子嗎?這就是和那個是一樣的道理。由於前面一個span是沒有inline boxes的節點,那麼它的基線就是元素底部,加上默認情況下是基線對齊,從而空白的span將往下掉。那怎麼辦呢?一種方法改變基線的位置,另一種方法是改變對齊的方式。改變基線的方式就是在空白的span裏面加入內容,如空格。我們把xxxx挪到span裏面,結果如下:

發現此時,空白奇蹟般的不見了。原因正是我們改變了基線的位置。同時由於設置了line-height爲0,所以就對齊了。另一種方法是改變對齊的方式,我們設置爲top,並去除所有的輔助線和字符,結果如下:

到此,完成了我們的應用。

  1. 爲什麼是近似垂直居中

前面我們多次提到“ 近似垂直居中 ”這個詞。爲什麼是近似,而不是絕對呢?問題還得回到這張圖。

當設置元素的對齊方式爲middle時,指的是: 元素的垂直中心線與父級元素基線的位置往上二分之一x高度所在線對齊 。換句話說,就是圖中的黃色線與紅色字母x的中心處對齊。但是文字具有下沉特性,原本x的中心點應該與圖中的白色線對齊,但是文字的下沉特性使字母往下掉了一點。從而導致黃色線無法絕對與白色線對齊。這個下沉的大小與文字的大小和字體有關。當文字大小足夠小時,我們可以忽略,近似的,白色線就與黃色線對齊,實現居中效果。但是文字大小很大時,就不能很好的實現了。我們還是以上面的例子:


 

x

img {
  width:  100px;
}
.wrap {
  background: #249ff1;
  margin: 10px;
}
.middle {
  vertical-align:  middle;
}

當我們設置line-height與height大小一直時,實現了垂直居中的效果。我們藉助畫圖工具往圖中做一些輔助線。

結果顯示,圖片的中線(紅色)並沒有與父級元素中線(橙色)完全重合。由於字符下沉的大小與文字的大小有關係,我們把父級的font-size設置爲100px,結果如下:

此時可以明顯的觀察到,圖片的上部空白比下部空白要大。其偏差正好是文字下沉的偏差。用圖片工具做輔助線觀察如下:

問題的根源找到了,那麼有沒有辦法實現絕對居中呢?答案是有的。解決辦法很明顯,如果我們讓父級元素的各種線重合在一起,也就是font-size設置爲0,就可以實現絕對居中了。給父級元素設置 font-size:0 ,結果如下:

五、總結

要掌握徹底 line-height 和 vertical-align 兩個屬性有很多細節需要去分析去發現,是有點難度的。但是我們如果弄清楚了常見的幾個特性和行爲,就能避免實踐的過程中遇到的一些坑。從而提高工作效率。通過以上的一些分析,總結以下幾點:

  1. 理清 line-height 與 height 之前的關係;
  2. 理清 vertical-align 不同值的表現行爲;
  3. 理解四種內聯盒子的關係;
  4. 理解基線的定義,包括 inline-block 的基線定義;
  5. 理解中線的定義,理清 middle 真正的表現方式;
  6. 知道字符有下沉特性
  7. 綜合掌握 vertical-align 和 line-height 之間的關係。

關於這部分的內容先寫到這裏,我有新的補充或者其他人有問題的可以反饋給我,我整理後完善文章。另外我在掘金上的文章均會同步到我的github上面,內容會持續更新,如果你覺得對你有幫助,謝謝給個star,如果有問題,歡迎提出交流。以下爲同步文章的幾個地址

1. 深入講解CSS的一些屬性以及實踐

2. Javscript相關以及一些工具/庫開發思路和源碼解讀相關

本文擴展閱讀: 字幕’x’在css世界中的角色和故事(張鑫旭)

閱讀提示:責編內容來自稀土掘金 【閱讀原文】。酷辣蟲無法對本文內容的真實性提供任何保證,請自行驗證並承擔相關的風險與後果!本站遵循[CC BY-NC-SA 4.0]。如您有版權、意見投訴等問題,請通過[eMail]聯繫我們處理。
徹底搞定vertical-align垂直居中不起作用疑難雜症

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章