【面試必備】CSS盒模型的點點滴滴

技術mix呢發表於2017-10-18

  從接觸CSS佈局開始,就一直在聽盒模型的概念了,網上的文章有很多,深淺不一。有些人會認為盒模型很簡單,不就是border、margin、padding、content嘛,一個元素所佔的空間就是把它們都加起來~僅僅如此嗎?當然不是,盒模型的概念其實挺好理解,但難的是與其他屬性一塊使用時產生的現象。下面把我知道的一些特性梳理一下,這也是css中最基礎的知識,面試常會問的東西。

盒模型的基本概念

  正如上面說的,盒子模型的幾個要素就是border、margin、padding、content,相信你已經聽過很多次了,就像下面這張百看不厭的圖一樣:

  不過要明確的是,這裡所說的盒子是指塊級(block-level)元素,行內元素有自己的一套標準,正如給行內元素設定margin-top無效一樣,不在這個盒子的討論範圍之內。本篇文章所說的盒子就是針對塊級元素。一個盒子其實是包含了四層,分別是content-box,padding-box,border-box,margin-box,它們的分界線就是上面畫出的那四個輪廓。這四個概念非常重要,後面我們要進行的各種邊界區分,就以它們為標準來討論。

  一個盒子在沒有被定位時,即沒有加position也沒有float,在普通流中所佔的空間就是這些值的總和。即橫向空間為:marginLeft + borderLeftWidth + paddingLeft + width + paddingRight + borderRightWidth + marginRight,縱向的同理。

  jQuery用多了,竟然忘記了如何用原生js來取這些值,所以在這裡也提一下。首先如果css樣式是寫在行內的,即元素的style屬性中,那麼直接通過element.style.marginLeft這樣就可以取到。如果樣式不在行內(通常情況都不在),通過style是無法取到的。IE和opera支援currentStyle屬性,可以取到定義在外部的樣式,其他瀏覽器則需要通過window.getComputedStyle來取,所以完整的獲取屬性的程式碼應該如下:

var element = document.getElementById('id');
var currentStyle = null;
if(element.currentStyle){
    currentStyle = element.currentStyle;
}
else{
    currentStyle = window.getComputedStyle(element);
}
console.log(currentStyle.marginTop);

寬度的計算

  從簡單的開始,我們給一個盒子指定樣式width:100px,是給上述四層盒子的哪一層來指定的呢?是content-box,即最內層的內容區域。另外我們也知道,元素的寬度會被它的padding撐開,實際的寬度會是width + padding。那border會不會撐開元素的寬度呢?這個。。平時寫邊框大多數就是1個畫素寬,基本忽略了。。。其實border也是會撐開寬度的。那margin會不會呢?都是外邊距了,已經在盒子外頭了怎麼會撐開呢,少年你想多了~

  這樣來看,光一個寬度就會有三種值,我們來舉個栗子:

<style>
#container{
    position: relative;
    width: 400px;
    height: 400px;
    background-color: lightgreen;
    overflow:hidden;
}
#box1{
    width: 100px;
    height: 100px;
    margin: 20px;
    padding: 20px;
    border:20px dotted;
    background-color: lightblue;
}
</style>
<div id="container">
    <div id="box1"></div>
</div>
box1
getComputedStyle(box1).width : 100px
box1.clientWidth : 140
box1.offsetWidth : 180

  我這裡姑且把它們分別叫做“指定寬度”、“實際寬度”、“偏移寬度”。指定寬度就是css樣式中的width值,實際寬度是被padding撐開後的寬度,由clientWidth屬性可訪問到,偏移寬度是被border撐開後的寬度,由offsetWidth可以訪問到。這裡我又想到了jQuery,jq中有兩種方式可以取到元素的寬度,它們取到的是哪個值呢,來看一下:

var $width = $('#box1').width(); 
console.log($width);//100

var csswidth = $('#box1').css('width');
console.log(csswidth);//100px

  可以看到兩者都是取到了指定寬度,不同的是.width()返回一個數字100,可以直接參與數值運算。而.css('width')返回了字串100px。

  上面是指定了寬度值的情況,如果不指定寬度的盒子是怎樣的呢?首先對於一個只有content-box的盒子,它會沾滿父元素的整行寬度,就像是指定了width:100%一樣,如下圖:

  如果指定了margin值,那麼這個content-box的寬度就變成了父容器的寬度減去margin值,即除了外邊距剩下的寬度:

  看來預設寬度挺好理解的,就是父容器減去margin值後剩餘的寬度,是能自適應的~

  但情況遠不是這麼簡單。當元素有float屬性或position為absolute時,寬度就不是自適應的了,而是會收縮起來,包裹住內容。例如我給粉色區域加了position:absolute,就變成這樣了:

  事實上,在給粉色元素加上display:inline-block時,也會收縮起來像上面一樣。inline-block的內容談起來也不是一篇文章能說清的,在本篇就不探討了。我們還是繼續聊寬度。

  我們在進行自適應佈局時,會使用百分比作為寬度單位,這樣頁面可以適應不同大小的螢幕。然而我們常常還需要設定一些比較小的間距,我在看響應式設計那本書時作者建議無論多小的長度都使用百分比,即你可能會寫出這樣的css程式碼:margin-left:0.3212332%; 當然這數值你用計算器算下的,如果能除盡,你可以把小數位數全寫出來,儘管不好看,但可以保證精確。但總有除不盡的時候,當我們進行四捨五入的時候,有時候稍稍大了那麼一點,就會出現元素被擠到下一行的尷尬場面。這個時候你多麼希望,那5px的邊距直接寫成5px就好,再也不必計算什麼百分比。

  囉嗦了這麼多,我其實就是想介紹一個css3的新特性而已。。。它就是calc,讓你的寬度值通過計算得出。最關鍵的時,它支援百分比和畫素混合計算,簡直太酷了,話不多說,上栗子:

#box3,#box4{
    height: 100px;
    padding: 10px;
    background-color: orange;
    border:5px solid;
    float: left;
}
#box3{
    width: 40%;
}
#box4{
    width: 60%;
}
<div id="container">
    <div id="box3">box3</div>
    <div id="box4">box4</div>
</div>
box3
box4

  在上面這種情況下,由於padding和border的存在,實際兩個div的寬度加起來要比100%大,所以box4就被擠到下一行了。下面我們來試試這個calc的威力,把box3和box4的寬度修改為:

#box3{
    width: -webkit-calc(40% - 30px);
}
#box4{
    width: -webkit-calc(60% - 30px);
}
box3
box4

  這下就不會換行了。需要注意的是calc是css3的新屬性,現在支援度還不高,需要加私有字首使用。另外要注意的一點是,運算子號+-(加減)與數值之間一定要留有空格,否則會被認為是非法的取值。而*/(乘除)運算子則不需要。

background的覆蓋區域 

   設定背景色我們平時用的太多了,不知你是否注意過,元素的背景到底是以哪個區域為邊界的呢,border-box?padding-box?還是content-box?不著急回答,我們需要分兩種情況,設定背景色和背景圖片。

  先說背景色,通過background-color設定的背景色總是填充滿border-box,我們用一個虛線框的元素就可以看出來,就像上面的box1一樣:

box1

  下面來看背景圖片的情況,通過background-image設定的背景圖片預設填充padding-box,看下面的程式碼:

<style>
#box5{
    width: 200px;
    height: 300px;
    margin: 20px;
    padding: 20px;
    border:30px dotted;
    background-image: url(http://images.cnblogs.com/cnblogs_com/lvdabao/507840/o_Img347406026.jpg);
    background-repeat: no-repeat;
}
</style>
<div id="box5">box5</div>
box5

  左上方從padding-box的區域開始填充,右側和下側超出後會超到border-box的區域,但不會超到margin-box,這也是合理的。 

  在背景圖片中,我之所以說“預設”而不是“總是”,是因為現在我們可以通過background-origin來設定背景圖片的填充區域了,這是css3的一個新屬性,用於設定了background-repeat為no-repeat的元素。它的取值有三種:padding-box、border-box、content-box,分別指定背景圖片的填充區域。這三個值正好對應我前邊說的盒子模型的概念。效果你應該能想象到,我們來試一下吧:

box5
background-origin:



 overflow:hidden;隱藏了哪裡?

  我們都知道overflow:hidden;可以隱藏掉內容超出元素的部分,但是你有沒有細細探究過到底是超出哪裡的部分呢?具體來說就是,超出content-box被隱藏?超出padding-box被隱藏?還是超出border-box?通過下面的例子便可一探究竟:

<style>
#box1{
    width: 100px;
    height: 100px;
    margin: 20px;
    padding: 20px;
    border:20px dotted;
    background-color: lightblue;
    overflow: hidden;
}
#box1_1{
    width: 200px;
    height: 200px;
    background-color: red;
}
</style>
<div id="box1">
    <div id="box1_1"></div>
</div>

  效果如下:

 

  可以看到紅色盒子被隱藏掉了padding-box以外的部分,子元素的內容並不會延伸到父元素的邊框上,這也是符合常規邏輯的。 

left/top的定位參照點

  當給一個元素指定position:absolute;後,即絕對定位,便可以參照它的包含塊(containing block)進行定位。所謂包含塊就是指父級元素中最近的擁有定位屬性的元素,即position值不為static的元素。通過left和top值來指定相對包含塊的左上角的偏移距離。那麼這個“左上角”具體是指哪裡呢?是下面ABCD四個點中的哪一個?同樣,元素自己的點又是取哪一個呢?

  通過下面的例子來探究一下:

<style>
#container2{
    position: relative;
    width: 400px;
    height: 400px;
    margin: 20px;
    padding:20px;
    border: 20px dotted;
    background-color: lightgreen;
}
#box6{
    position: absolute;
    width: 100px;
    height: 100px;
    padding: 20px;
    border: 10px dotted;
    background-color: red;
}
</style>
<div id="container2">
    <div id="box6"></div>
</div>
 

  通過點選上面的按鈕,我們可以得出以下幾個結論:

  • 元素自身的參照點是最外圍的盒子,即margin-box,如果沒有margin就以border-box,以此類推。
  • left/top值為預設值(auto),元素還在原來的位置上,即緊貼父元素的content-box區域左上側。
  • left/top指定值後,參照父元素的padding-box左上角,即我們上面圖中的C點。 

取負值的情況

  border、padding取負值都是非法的,而margin取負值是合法的,而且還大有所用。簡單來說,margin會影響元素的寬高,使元素往回“縮”一定的長度。看下面的例子:

<style>
#box7,#box8{
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
}
#box7{
    background-color: green;
    margin-bottom: -100px;
}
</style>
<div id="box7">box7</div>
<div id="box8">box8</div>
box7
box8

  儘管box7的高度指定為200px,但因為margin-bottom:-100px讓它縮回去100px,所以只剩100px的高度了。關於margin負值的應用場景非常的經典,我這裡就不一一列舉了,可參考瑤姐的這篇文章:http://blog.doyoe.com/~posts/css/2013-12-06-margin%E7%B3%BB%E5%88%97%E4%B9%8B%E5%86%85%E7%A7%80%E7%AF%87.md 

BFC

  BFC同樣也是盒模型中非常重要的一個概念,出於篇幅的原因,我不能詳細的解釋了,有一篇文章介紹的非常好,可參考http://blog.melonhuang.gitpress.org/~docs/css/1formattingContext.md

相關文章