CSS之垂直水平居中的背後

2022-09-14 18:05:58

  最開始,我想說,這個體系有點大,我寫的並不好。就當作是一個思路吧,雖然這個思路有點亂。幾乎每一個實現方案的背後都是該屬性及其組合的原理,每一個都要剖析其規範細節的話,這篇文章絕不會是這樣的篇幅,所以每當需要說更多的時候我都簡單闡述後一筆帶過,然後附上參考資料。後面若是寫css系列的時候再詳細講吧。

  其實這道面試題,真的考察很多東西,個人理解它屬於開放型別的問題,沒有指定範圍的答案,所以它就可以涉及到很大範圍的知識概念,並且以此判斷開發者對技術理解的深度和寬度。那麼要解決這個問題,最大的難點在於分類。首先,從題目上來說,可以分為垂直居中、水平居中,子元素確定寬高下的水平居中、子元素確定寬高下的垂直居中,甚至於父元素確定寬高、不確定寬高,父元素子元素都確定寬高,都不確定寬高等等情況。其次,從技術解決方案的角度上來說,又可以有比如Flex、比如Margin、比如Position等。再者,基於不同的技術手段,其實還要區分display的屬性值,也就是盒子的型別。

  所以在解決問題的切入點上就很難區分要以什麼樣的角度去分類,從而作為後面解題的基礎,本菜雞就以知識點也就是css屬性為切入點來分類,針對不同場景的父子盒子的垂直水平提出解決方案。

  首先,我們先寫一個demo程式碼,我們後面所有的垂直水平居中解決方案的基礎程式碼環境都是基於這個demo的。完整程式碼如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #father {
        box-sizing: border-box;
        width: 200px;
        height: 200px;
        border: 1px solid;
      }
      #child {
        box-sizing: border-box;
        width: 50px;
        height: 50px;
        border: 1px solid;
        background: red;
      }
    </style>
  </head>
  <body>
    <button id="fatherBtn">切換父盒子寬高</button>
    <button id="childBtn">切換子盒子寬高</button>
    <div id="father">
      <div id="child"></div>
    </div>
  </body>
  <script>
    let father = document.getElementById("father");
    let child = document.getElementById("child");
    let fatherBtn = document.getElementById("fatherBtn");
    let childBtn = document.getElementById("childBtn");
    fatherBtn.addEventListener("click", function () {
      father.style.width = father.offsetWidth + 30 + "px";
      father.style.height = father.offsetHeight + 30 + "px";
    });
    childBtn.addEventListener("click", function () {
      child.style.width = child.offsetWidth + 15 + "px";
      child.style.height = child.offsetHeight + 15 + "px";
    });
  </script>
</html>

  就很簡單,頁面看起來就是這個樣子:

 

  點選對應的按鈕,可以分別增加父子盒子的寬高。demo程式碼就是這樣,so easy,下面就開始我們的水平垂直居中解決方案之路。

第一部分 獨立

  這一部分,我只提供某一個CSS屬性所提供的獨立的能力,比如它可以實現垂直居中,或者水平居中,或者可以實現垂直水平居中。讓我們深入理解單獨屬性的能力。

一、Grid

  網格佈局,它可以將頁面劃分成一個個可以任意組合的網格,以前這樣的處理只能通過複雜的css框架達到預期的效果。現在,瀏覽器內建了這樣的能力。Grid佈局與Flex佈局有一定的相似性,都可以指定容器內部多個專案的位置。但是,它們也存在重大區別。

  Flex 佈局是軸線佈局,只能指定"專案"針對軸線的位置,可以看作是一維佈局。Grid 佈局則是將容器劃分成"行"和"列",產生單元格,然後指定"專案所在"的單元格,可以看作是二維佈局。Grid 佈局遠比 Flex 佈局強大。

  採用網格佈局的區域,稱為"容器"(container)。容器內部採用網格定位的子元素,稱為"專案"(item)。你看,Grid也有類似於Flex的定義。但是Grid的針對容器的劃分要比Flex複雜得多。

  Grid容器中的水平區域稱為行,垂直區域稱為列,行與列的交叉區域叫做單元格。誒?這不是跟表格的命名很像?嗯~~幾乎一模一樣。

  劃分網格的線,稱為"格線"(grid line)。水平格線劃分出行,垂直格線劃分出列。正常情況下,n行有n + 1根水平格線,m列有m + 1根垂直格線,比如三行就有四根水平格線。

  上面那段話也是我複製下來的,詳細的內容就在末尾的參考資料裡,有興趣可以深入的學習。現代瀏覽器的支援情況還是可以的。

  那麼,針對本篇的問題點,基於Grid要如何實現垂直水平居中:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: grid;
  justify-content: center;
  align-content: center;
}

  程式碼也十分簡單。

  這一小節我們所貼上的程式碼十分的少,甚至沒有過多說明grid的各種屬性及其能力。我們要注意的是,在使用grid的情況下,實際上我們已經更改了盒子的display值,也就是使用grid的盒子已經不再是單純的block了。

  grid可以實現水平居中並且響應性的無論是inline還是block的本質是因為它是grid,不是inline也不是block。

二、Flex

  Flex佈局是W3C在2009年提出的一種新的佈局解決方案,可以簡便、完整、響應式的實現各種頁面佈局。目前它已經得到了所有瀏覽器的支援,這意味著現在就可以很安全的使用這項功能。好吧,我相信大家都已經使用Flex技術使用的非常順手了。具體的細節內容大家可以參考本篇末尾的參考資料部分。

  我們來了解下Flex佈局下的基本技術概念。採用 Flex 佈局的元素,稱為 Flex 容器(flex container),簡稱"容器"。它的所有子元素自動成為容器成員,稱為 Flex 專案(flex item),簡稱"專案"。基於此,其中一部分css屬性是針對容器的,比如flex-direction、justify-content、align-items等。還有一部分屬性是針對專案的,比如flex-grow、flex-shrink、flex- basis等。

  然後,我們要注意的一點就是,容器預設存在兩根軸,水平的主軸(main axis)和垂直的交叉軸(cross axis)。主軸的開始位置(與邊框的交叉點)叫做main start,結束位置叫做main end;交叉軸的開始位置叫做cross start,結束位置叫做cross end

  專案預設沿主軸排列。單個專案佔據的主軸空間叫做main size,佔據的交叉軸空間叫做cross size

  其實上面大部分的內容都來自於參考資料,其實基於Flex的水平垂直居中的解決方案十分簡單,你肯定也知道,就是這樣:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: flex;
  justify-content: center;
  align-items: center;
}

  我只加了三行程式碼,就可以實現我們理想中的垂直水平居中,非常簡單、快捷、舒適。

  你看,解決方案就這麼簡單。另外,我尤其要強調的一點是,Flex佈局,已經不再是單純的盒模型,它是Flex Container,即彈性容器。我們通過設定display屬性為flex,改變了盒子的型別。這個大家尤其要注意點。

  最後,大家可以看到,針對垂直水平居中的Flex和Grid的解決方案都比較簡單,但是,它們的詳細內容及核心要點我都沒寫,都在文末的參考資料中,那裡才是你要關注的重點,我僅僅提供瞭解決方案的一種思路,說得多了,真的覺得沒意義。一定要去看文末的參考資料去學習其基本原理。嗯~~參考資料才是本篇的重點。

  那麼接下來,我們繼續看看其他解決方案。

三、Transform

  不知道大家對這個東西熟悉不熟悉,CSS transform 屬性允許你旋轉,縮放,傾斜或平移給定元素。注意給定元素這個詞,也就是說,transform屬性,是針對自身來進行變換的。那我們思考一下,針對自身,也就是說我無法知道父盒子的一些資訊,比如寬高啊啥啥的。所以,transform能做到的,只是這樣:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  translate: 75px 75px;
}

  注意,這回我們是改的子元素了噢。上面的程式碼~~誒~~~誒?!不是transform麼?你咋可以直接寫translate?CSS 屬性 translate 允許你單獨宣告平移變換,並獨立於 transform 屬性。嗯~就是translate可以單獨寫,跟transform一樣:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  transform: translate(75px, 75px);
}

  展示的結果,如我們所料:

  那麼文字是不是也可以呢?我們修改下demo:

<body>
  <div class="btn-area">
    <button id="fatherBtn">切換父盒子寬高</button>
    <button id="childBtn">切換子盒子寬高</button>
  </div>
  <div id="father">
    <div id="child">我是文字我</div>
  </div>
</body>

  我們加點字,然後改下css:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  transform: translate(75px, 75px);
  display: inline;
}

  直接改成inline,你會發現:

  不行。。。inline不行。但是inline-block卻可以。額~~這個說起來有點複雜,簡單說的話就是規範的定義就是這樣的,設定了transform屬性的元素叫做可轉換元素。然後,我找了一個小時找到了這句話:

all elements whose layout is governed by the CSS box model except for non-replaced inline boxes, table-column boxes, and table-column-group boxes

   啥子意思呢,就是盒子型別除了是inline、table-column以及table-column-group的都可以使用transform。不信你試試呢?我肯定不會一個一個去試的,但是我確實試了幾個,好像確實是這樣。

  如果只是單純的translate,只能移動自身,只能在父子元素都是固定寬高的情況下實現垂直水平居中,或者說,translate本身就與其它內容無關,只與自己有關,因為是自身的transform,一旦父子元素的寬高變化,它肯定就無法垂直水平居中了。

  還有個問題,為什麼是75px?這個75px是怎麼計算出來的?就是父盒子width/2 - 子盒子width/2。其實就是一半減一半啦~~

  那我用百分比不行麼?不行,因為你不知道父盒子的寬高,而如果一定要只使用translate,只能在盒子變化的時候通過js來計算父子盒子的寬高,從而重新設定子元素的translate基於自身移動的X軸與Y軸的值。不信我們試一下:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  transform: translate(100%, 100%);
}

  效果是這樣的:

 

   誒?transform只跟自身有關係,所以它translate的100%其實是50px,那我不是可以這樣:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  transform: translate(150%, 150%);
}

  嗯……好像沒毛病,但是你這樣就是強硬計算的。跟75px沒區別。但是我們仍舊強調了一點就是,transform只與自身有關係transform只與自身有關係!transform只與自身有關係!transform只與自身有關係!重要的事情說了五六遍了。你記住了吧?

  其他的可能,就是不可能。額……在只有translate的前提下。

  OK,translate告一段落。

四、Position

  position屬性想必大家都很熟悉了,相對定位、絕對定位、粘性定位、固定定位啊,啊不,沒有等等,啊不不不,還有等等,不重要了~

  position定位的位移計算方式與父子盒子無關,不同的定位方式,僅影響它相對計算的方式。具體的內容細節我不多說,去檔案看吧。我要說的是,position的位移依賴於top、right、bottom、left屬性。

  position適用於:

all elements except table-column-group and table-column

  也就是除了table-column-group和table-column的所有display屬性值。和transform的區別就是inline也可以使用position

一)相對定位(Relative,Sticky)

1、relative

  我們來看下程式碼:

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  position: relative;
  top: 75px;
  left: 75px;
}

  效果是這樣的:

 

   看起來還可以,跟translate的效果是一樣的,但是實際上這樣做的問題很多,並不適用大多數的實際應用場景,有很大的侷限性。當我們切換父子盒子的寬高的時候,只要父盒子的一半減去子盒子的一半等於你設定的寬高,那麼就可以實現水平垂直居中的效果。

  那麼百分比呢?其實百分比也跟translate是類似的。我就不再寫了。要注意的是,relative是相對於自己所在位置進行相對位移的。所以,在本例的程式碼中展現的效果和translate十分類似。

2、sticky   

  粘性定位,實際上要依賴於捲動盒子。sticky的計算方式依賴於最近的捲動祖先。注意,一個 sticky 元素會「固定」在離它最近的一個擁有「捲動機制」的祖先上(當該祖先的overflow 是 hiddenscrollauto, 或 overlay時),即便這個祖先不是最近的真實可捲動祖先。

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  overflow: scroll;
}
#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  position: sticky;
  top: 75px;
  left: 75px;
}

  但是這個例子由於父盒子沒有被撐開卷動,所以不太能看出來捲動的效果,我們改下例子:

<div id="father">
  我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我
  <div id="child">我是文字我</div>
  是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字我是符合子的文字
</div>

  不用修改css,只是讓父盒子可以被文字撐開即可:

   我們就可以看到這樣的效果,它的本體跟relative一樣,也沒有脫離檔案流。所以relative和sticky其實都是相對定位

二)絕對定位(Absolute、Fixed)

  絕對定位和相對定位有一個本質的區別,就是絕對定位會脫離檔案流,形成一個獨立的圖層。由於絕對定位可以脫離檔案流,與正常DOM元素的排版互不影響,所以絕對定位要比相對定位的使用場景更加廣泛。

1、absolute

  在垂直水平居中的場景下,absolute要比relative更加的有意義。因為大多數的後面的關於不定寬高的垂直水平問題的討論,論點,論據,證明等等幾乎都離不開absolute。我們來看下程式碼:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  position: relative;
}
#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  position: absolute;
  top: 75px;
  left: 75px;
}

  這樣做,跟上一小節的relative部分沒有區別。一個意思。嗯~~沒了?沒了……。單純的absolute只能做到這樣,跟relative一樣。

2、fixed

  fixed和absolute的區別只是定位計算的相對元素不同,absolute是相對於最近的具有定位屬性的祖先元素,而fixed則直接相對於瀏覽器的視口來計算定位。例子~~嗯~~沒有例子。

五、Margin

  額~margin我想大家都知道它能幹什麼了,盒模型的外邊距。可以通過設定margin:0 auto;從而讓該元素水平居中。

#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  margin: 0 auto;
}

  看起來就像這樣:

  其實,我們這樣寫也行:

margin: 75px;

  就垂直水平居中了:

  那麼還要強調一點的是,margin和padding的百分比計算參照是父盒子的寬度。無論margin和padding設定的是上下左右哪一個,它百分比計算的參照都是父盒子的寬度

六、Padding

  如果我想用padding設定垂直水平居中,你猜是否可以?

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  padding: 75px;
}

   效果是這樣的:

   嗯,其實這個原理跟之前相對於父元素移動的transform和position類似。但是也僅僅如此了。

  那記不記得我在margin那部分說過的一句話,假如我這樣設定:

padding:10%

  這個10%是多少?嗯。。我也不知道,你當前設定padding屬性的父盒子的寬度的10%。

七、Line-height

  利用line-height,我們可以這樣:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  line-height: 200px;
}
#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  display: inline-block;
}

  效果是這樣的:

   誒?誒誒餓誒?你這不太對啊,我這膀胱一掃你這子元素就沒垂直居中啊。那我改一下:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  line-height: 225px;
}

  誒?好像可以了:

   你說這是為啥,200px咋就不對勁呢?嗯~~因為基線問題。但是line-height確實可以讓我們的行內塊元素垂直居中。

八、Text-align

The text-align CSS property sets the horizontal alignment of the inline-level content inside a block element or table-cell box. This means it works like vertical-align but in the horizontal direction.

  就是說其實text-align跟vertical-align類似,只不過text-align是水平,vertical-align是垂直。

  利用text-align,我們可以這樣:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  text-align: center;
}
#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  display: inline-block;
}

  嗯。。好像沒啥問題:

九、Vertical-align

  vertical-align是用來確定行內元素或者表格單元格的垂直對齊方式的。它可以使行內元素垂直居中。我們以Table為例來講解下vertical-align。

  table這個東西其實有點奇怪並且很少使用,但是它也確實能實現垂直水平居中。利用table,一種是修改DOM,寫個table的結構:

<table>
  <tbody>
    <tr>
      <td class="father">
        <div class="child"></div>
      </td>
    </tr>
  </tbody>
</table>

  CSS是這樣的:

.father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
}
.child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
}

  我們沒有給css新增任何除了寬高背景外的屬性。

  效果是這樣的:

  我們審查下DOM,可以看到:

 

   tbody的vertical-align是middle,而td則是這樣:

   它一級一級的繼承下來了。不信大家可以親自試一試

   確實垂直居中了,因為table-cell天然垂直居中,當然,它垂直居中的原因則是因為table的DOM會預設賦予一些css屬性,比如vertical-align。另外一種就是不用修改DOM,而是修改css屬性:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: table-cell;
  vertical-align: middle;
}

  效果是一樣的:

   到最後我們發現,垂直居中的根本原因並不是table,而是vertical-align。

十、writing-mode

  這個東西,好像大家都不怎麼常用。但是,它確實提供了一種垂直水平居中的解法。當然,它本身無法實現垂直水平居中,它只是改變了檔案流的流向。writing-mode定義了文字水平或垂直排布以及在塊級元素中文字的行進方向。writing-mode這個東西其實理解起來也不算複雜,只不過它的關鍵字有點煩人。writing-mode目前有五個屬性:horizontal-tb、vertical-rl、vertical-lr以及瀏覽器支援情況一點都不好的試驗性屬性sideways-rl和sideways-lr。具體屬性的含義,請看文末的MDN連結或自行學習。

  我們來寫個例子試一下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .father-mode {
        width: 100px;
        border: 1px solid;
      }
    </style>
  </head>
  <body>
    <div class="father-mode">
      我是文字編輯,今年390歲,喜歡吃美食,喜歡看美女,喜歡遊美景。
    </div>
  </body>
</html>

  效果就是這樣的:

   就很正常。

然後我們加點程式碼:

.father-mode {
  width: 100px;
  border: 1px solid;
  writing-mode: vertical-lr;
}

  

   有點長,嗯~~換句話說,利用writing-mode可以改變檔案流的流向,嗯,大家記住這句話就可以了。

第二部分 組合

  上一個部分,我們花了不小的篇幅去整理一些在垂直水平居中問題上可以用到的css屬性,我都是單獨拎出來簡單說明的。這部分內容也並不簡單,我並沒有一個屬性一個屬性完全的拋開了揉碎了去講,只是在限定的條件下展示了某一個屬性可以居中的能力。當然,還提了一些百分比的相對計算方式,也就是css單位的計算方式,css單位也是一個很複雜的體系,大家要詳細的去了解學習。

  我們簡單總結下第一部分的內容。大概可以這樣來分:

  1. 佈局:Flex、Grid
  2. 盒模型:Margin、Padding
  3. 位移:Translate、Position
  4. 行內對齊:Text-align、Vertical-align、Line-height
  5. 流:Writing-mode
  6. 計算單位。

  我特意加上了計算單位,因為它真的很重要,很多面試的時候,也會問這個問題。但是就計算單位再加上對應可以使用該單位的屬性來說,完全可以再寫一篇部落格了。所以我可能後面會寫關於單位的部落格。這裡肯定就不多說了。

  那麼接下來,我們就使用以上的內容,組合起來,實現不定寬高垂直水平居中。

  在實現之前,我們想想,我們大概會用到哪些屬性?熟悉flex和grid肯定是不會用的,因為它們自成一套佈局體系。只要使用這個體系,就可以自動的響應式的實現居中效果。接下來就剩下盒模型、位移、行內對齊以及流,再配合計算單位,來實現對應display值的垂直水平居中。

  注意,我們下面實現的前提是不定寬高的父子元素的垂直水平居中噢。

一、absolute + transform

  這種解決方案是最好理解的方式。因為它牽扯到的屬性及屬性值都比較表面,就是書寫的那種意思。我們來看下:

#father {
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border: 1px solid;
  position: relative;
}
#child {
  box-sizing: border-box;
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

  其實這段程式碼就涉及到了兩個點,absolute位移的計算方式,translate位移的計算方式。由於absolute的相對計算位置是最近定位的祖先元素,並且是從左上角的原點開始計算,所以當position位移上、左各50%的情況下,子元素從左上角計算移動了50%。就是這樣:

 

 

   所以,我們就需要讓自身再往左往上移動自身的一半距離,所以,我們利用translate是移動自身的特性,通過百分比單位,移動自身的一半距離。

 

 

   就實現了我們想要的結果。這種方式可以自適應任何寬高的父子元素。你可以試試~,至於原理,就是利用百分比單位來得到父子元素的寬高,最後利用相對計算方式不同的position和translate來進行移動。

  嗯~這是第二部分最好理解的一種方式了。繼續~~

二、absolute + margin: auto

  這種方式我們要學的東西就稍微多了點,我們先看解決方案:

#father {
  box-sizing: border-box;
  height: 200px;
  width: 200px;
  border: 1px solid;
  position: relative;
}
#child {
  box-sizing: border-box;
  border: 1px solid;
  background: red;
  position: absolute;
  width: 50px;
  height: 50px;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  margin: auto;
}

  這種解決方案看起來還不錯。

  但是~~我們後面再說但是。

  我們先看首先absolute的上下左右距離都是0,還設定了margin為auto。按理來說,如果當前子盒子沒有寬高,的話你的absolute的距離都設定為0,那麼應該是會撐滿父元素的:

#child {
  box-sizing: border-box;
  border: 1px solid;
  background: red;
  position: absolute;
-  width: 50px;
-  height: 50px;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

  效果就是這樣的:

 

 

   這就意味著,當前的方案子元素必須是有寬高的。加上寬高的話,就變成這樣了:

 

 

   好像跟沒加一樣,嗯……好像是。但是一旦加上margin:auto。就可以垂直水平居中了,為啥不是margin: 0 auto;。必須是margin: auto;呢。auto的背後有複雜的計算方式,僅在當前的場景下,我們給子元素設定margin:auto;它會自動計算剩餘空間,然後平分,這也是為什麼子元素必須有寬高的原因。

三、line-height + text-align + vertical-align

  嗯,不建議大家使用這種方式對佈局實現垂直水平居中,你可以針對文字使用,因為這幾個屬性的本身設計就是如此,不要為賦新詞強說愁,所以,下面的例子,僅作為方案:

#father {
  box-sizing: border-box;
  height: 200px;
  width: 200px;
  border: 1px solid;
  line-height: 200px;
  text-align: center;
  font-size: 0px;
}
#child {
  box-sizing: border-box;
  border: 1px solid;
  background: red;
  width: 50px;
  height: 50px;
  font-size: 16px;
  display: inline-block;
  vertical-align: middle;
  line-height: initial;
  text-align: left;
}

  這種方案,因為涉及到line-height,所以可以想想到,父盒子的寬高一定是確定的。我們可以去掉子元素的50px寬高試一下:

 

 

   嗯,就是inline-block的表現。我們分析下每個屬性在父子元素中所起的作用吧,首先,我們在父盒子中設定了line-height和text-align,按理來說現在子元素就應該是垂直水平居中的了,所以只要我們讓子元素變成行內塊即可。為啥還要在父元素上加個font-size: 0;呢?因為字型下沉,不信你去掉font-size: 0;,你會發現位置變化了。

  然後,子元素的那些其他的屬性,都是為了重置父元素繼承所帶來的影響。完事了~,這種方式,影響大,程式碼多,理解度倒是還好。總之不建議在佈局中使用。

四、writing-mode + text-align + vertical-align

  說實話我不太想寫這個東西,但是由於第一部分寫了writing-mode,而且它確實有一定的使用場景,當然不是在這。所以還是寫一下吧。

  再說句實話,我寫到這裡已經寫的有點煩了~~為啥……,因為不能多說,多說就是每個屬性的規範,又不能不說,就是咽咽不下去,吐吐不出來的感覺,好難受~

  好吧,我們看程式碼,這回的程式碼,有點噁心,不建議在生產中使用這套方案來實現垂直水平居中!

 

#grandpa {
  writing-mode: vertical-lr;
  text-align: center;
  box-sizing: border-box;
  height: 200px;
  width: 200px;
  border: 1px solid;
  background: pink;
}
#father {
  writing-mode: horizontal-tb;
  display: inline-block;
  text-align: center;
  width: 100%;
  border: 1px solid;
  background: blue;
}
#child {
  border: 1px solid;
  background: red;
  display: inline-block;
  margin: auto;
  text-align: left;
}

 

  DOM是這樣的:

<div id="grandpa">
  <div id="father">
    <div id="child">我是文字我</div>
  </div>
</div>

  效果是這樣的:

 

 

   嗯。。。。這個為啥需要三層?因為它先改變了爺爺那層的文字流向,就影響了爸爸,然後爸爸在讓兒子居中,就實現了。但是,這種東西,已經不適合生產實踐了。只能作為學習理解。

  好啦,本篇到此終於結束了,其實還有些方案我沒寫,比如absolute+負margin,比如absolute+calc,我覺得現在這些差不多可以了,足夠表達出我想表達的內容了。

  這篇文章想要提供給你的並不是魚,而是漁,嗯……希望我做到了。

  最後~如果你能看到這裡,感謝螢幕前的你能把這麼混亂的一篇文章看完,嘻嘻~

參考資料: