這一章主要講解下CSS在渲染的時候的理論原理。沒人或書籍能夠覆蓋到css的所有細(xì)節(jié),所以在我們?nèi)粘J褂弥杏龅絚ss的問題的時候,了解CSS的渲染邏輯將有效幫助大家排查問題。
基本的盒模型
CSS的基本思想就是假定每個元素都是由1個或多個盒子組成的,稱為element boxes。在每個box的中間有一塊內(nèi)容區(qū)域(content area),而包圍內(nèi)容區(qū)域的就是像padding, border, outline和margin的各種可選邊框,之所以說是可選的因為所有這些邊框的默認(rèn)寬度都是0,如下圖所示:

像padding,border和margin都有各自的設(shè)置選項,但outline就沒有了。另外內(nèi)容的背景也只應(yīng)用與padding內(nèi)部,margins都設(shè)置為透明的,這樣可以讓所有父元素的背景都看的見。padding是不準(zhǔn)有負(fù)數(shù)的,但margin卻可以,在后面咱們會詳細(xì)討論。
我們先從border開始,border類型包括solid和inset(inset就是內(nèi)嵌的意思,看起來效果像凹槽)。border的顏色通過border-color設(shè)置,如果沒有被設(shè)置的話,那就默認(rèn)采用前景色(foreground color,不清楚么,如果說元素內(nèi)容的顏色是不是清楚了),最后要記住border的寬度是不準(zhǔn)設(shè)置負(fù)值的。
快覽
首先我們來解釋下后面要用到的幾個重要的概念:
Normal flow:正常的流
這個指的是文本默認(rèn)的渲染順序,一般語言都是一樣的:從左到右,從上到下。而在某些特殊的語言中會有所不同,而咱們HTML語言其實也是遵循這一規(guī)則的,既然前面說過每個元素都是box,所以正常的流的意思就是,元素的box默認(rèn)以這種順序排列。那怎么樣才能跳出這種normal flow呢?方法也有很多,可以設(shè)置為float, position, 或是flexible box 和grid layout。Nonreplaced element: 無法替代的元素
這主要指那種內(nèi)容放在document的元素,舉個例子,<p>就是一個無法替代的元素,因為其文本內(nèi)容就在元素自己當(dāng)中。Replaced element: 可替代元素
和上面相反,這里的元素是可以替換的,例如img元素,他其實就是指向一張圖片;同樣,大部分的表單元素也都是可替換的。Root element: 根元素
這個我們很熟悉了,就是document文檔樹的根節(jié)點,像咱們常用HTML文檔,其根節(jié)點就是html;而在XML文檔中,根節(jié)點可以任意指定,例如RSS文件的根節(jié)點就是rss。Block box
block的盒子它就像積木一樣是縱向堆疊的,常用的有段落p,標(biāo)題h1~6,和最常用的div。想要生成block盒子的元素可以通過聲明display:block來實現(xiàn)。Inline box
跟block相對的就是inline box, 常見的inline類型有strong和span等。如果想要變成inline box 只要聲明display:inline就好。Inline-block box
這個box在內(nèi)部表現(xiàn)的像block,而在外部表現(xiàn)的像inline。就是有點像可替換元素。
除了以上的這幾種盒子,其實還有像table-ce的其他盒子,由于各種原因就不一一細(xì)說了。
containing block
對于CSS的盒子來說,父元素的box是很重要的,類似于編程的繼承,子元素也會繼承父元素的框架上下文(layout context)。在CSS中定義了一系列的規(guī)則來確定盒子的containing block。
來看個具體的例子:
<body>
<div>
<p>This is a paragraph.</p>
</div>
</body>
p的containing box就是div,而div的就是body。而這個body因為是第一個元素被稱為初始的containing box,之所以這么特殊,單獨命名因為視口viewport決定了該元素的布局大小,而不是由根元素的內(nèi)容大小決定的。
調(diào)整element顯示display
在css中應(yīng)該是最重要的一個屬性了:


改變默認(rèn)的顯示方式
<nav>
<a href="index.html">WidgetCo Home</a>
<a href="products.html">Products</a>
<a href="services.html">Services</a>
<a href="fun.html">Widgety Fun!</a>
<a href="support.html">Support</a>
<a href="about.html" id="current">About Us</a> <a href="contact.html">Contact</a>
</nav>
nav默認(rèn)誰inline的布局,如果我們想要改變他的顯示方式,可以這樣設(shè)置:
nav a {display: block;}
這會使所有的a元素都以block的元素來呈現(xiàn)。

下面再考慮另一種情況,就是把block的元素改變從inline的
<ul id="rollcall">
<li>Bob C.</li>
<li>Marcio G.</li>
<li>Eric M.</li>
<li>Kat M.</li>
<li>Tristan N.</li>
<li>Arun R.</li>
<li>Doron R.</li>
<li>Susie W.</li>
</ul>
#rollcall li {display: inline; border-right: 1px solid; padding: 0 0.33em;}
#rollcall li:first-child {border-left: 1px solid;}

雖然大部分時候都可以這樣來操作,但是h5本身是有限制的,即block元素包含inline元素可以,但是inline元素中包含block是不允許的,尤其是對于某些默認(rèn)的inline和block元素,來看個例子:
<span style="display: block;">
<p style="display: inline;">this is wrong!</p>
</span>
上面的強行轉(zhuǎn)換其實是沒用的,display只能改變元素的展示,而不乏改變元素的類型,所以有些跟類型強綁定的屬性是無法通過display來修改的。
接下面我們就來看看不同的box:包括block box, inline box, inline-block box 和list-item box。
Block box
block 盒子其實用的是最多的布局元素,但有時候其顯示會超出你的預(yù)期,這就需要對該盒子的某些邊界有清楚的了解,來看看下面這張圖:

默認(rèn)來說,block box的寬度設(shè)置的是left inner edge 到right inner edge的距離,height同理。
不過我們可以通過設(shè)置box-sizing屬性來修改這一規(guī)則:

這個屬性就是用來設(shè)置widht和height的定義規(guī)則的,默認(rèn)的上面說了,如果設(shè)置為border-box,那么其寬度的界定將基于左右border邊界的距離,同理可知padding-box的含義。

box-sizing水平的變化
通過改變box-sizing會導(dǎo)致水平方向計算的變化,來看個例子:
<p style="width: 200px; padding: 10px; margin: 20px;">wideness?</p>

水平方向的屬性
共有7個屬性決定水平的長度:margin-left, border-left, padding-left, width, padding-right, border-right 和 margin-right。具體參照下圖:

之前有提到過的一個containing block的概念,其寬度就是這7中長度的和。而在這7個值里面,只有三個是默認(rèn)設(shè)置為auto的,那三個呢?左右的margin和width,其余的值都需要設(shè)置數(shù)值,有些的默認(rèn)值為0.

而width只能出現(xiàn)2中情況,auto和非負(fù)的數(shù)值。
使用auto
auto的意思就是自動調(diào)整,調(diào)整的目標(biāo)就是子元素的整個長度等于父元素的寬度。
那有人就問啦,如果三個auto都設(shè)置了具體值,那怎么計算呢?這在CSS中有個專有名詞overcontrained,意思就是限制過多了,那么css就用將margin-right強制設(shè)置為auto。
div {width: 500px;}
p {margin-left: 100px; margin-right: 100px;
width: 100px;} /* right margin forced to be 300px */

如果左右margin都設(shè)置了,但width為auto,那么width就承擔(dān)起自動填充的任務(wù):
p {margin-left: 100px; margin-right: 100px; width: auto;}
多個auto的情況
比如margin都設(shè)置為auto,那么左右都會設(shè)置相同的長度,來看個例子:
div {width: 500px;}
p {width: 300px; margin-left: auto; margin-right: auto;}
/* each margin is 100 pixels wide, because (500-300)/2 = 100 */

所以很多想要居中的內(nèi)容都是講margin設(shè)置為auto來實現(xiàn)的。
最后一種情況就是三個都設(shè)置為auto,那么margin就會都被設(shè)置為0,width會盡量填充父元素寬度。
負(fù)的margin
來看個例子:
div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: auto; margin-right: -50px; }

看起來已經(jīng)超出了父元素的范圍,但在數(shù)學(xué)上還是對的:
10px+0+0+540px+0+0?50px = 500px
還記得之前說的auto么,在這里,我們同樣可以通過設(shè)置auto來抵消子元素的邊界超出問題:
div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: 600px; margin-right: auto;
border: 3px solid gray;}
對應(yīng)的公式如下:
10px+3px+0+600px+0+3px?116px = 500px
記住padding, border 和width是不能有負(fù)數(shù)的。
百分比
有時候百分比還是很有用的,尤其是在動態(tài)確定寬度比例的情況下:
<p style="width: 67%; padding-right: 5%; padding-left: 5%; margin-right: auto; margin-left: 5%;">playing percentages</p>
不過如果要混合使用百分比和具體數(shù)值的話會讓人很困惑:
<p style="width: 67%; padding-right: 2em; padding-left: 2em; margin-right: auto; margin-left: 5em;">mixed lengths</p>
值的注意的是border是不接受百分比值的。
可替換元素
對于圖片元素這種可替換元素來說,如果width指定為auto,那么元素的width將會是元素內(nèi)容的實際寬度:
<img src="smile.svg" style="display: block; width: auto; margin: 0;">
當(dāng)然我們應(yīng)該設(shè)置圖片的實際寬度:
<img src="smile.svg" style="display: block; width: 25px; margin: 0;">
<img src="smile.svg" style="display: block; width: 50px; margin: 0;">
<img src="smile.svg" style="display: block; width: 100px; margin: 0;">

垂直方向的格式化
剛說完水平的,下來來看看垂直高度的幾個有趣的地方。首先是一個元素的內(nèi)容決定了元素的默認(rèn)高度,同時內(nèi)容的寬度也會影響高度。那就會出現(xiàn)兩個問題,自己設(shè)置的高度高于內(nèi)容高度,自己設(shè)置的高度低于內(nèi)容高度。對于前者,會保留正常的空白;而對于后者,則依賴于另一個屬性overflow:

垂直方向?qū)傩?/h2>
對照水平方向,也是有7個屬性:margin-top, border-top, padding-top, height, padding-bottom, border-bottom, and margin-bottom.

跟水平一樣,垂直方向的auto也是這三個:

auto的觸發(fā)規(guī)則與水平方向是一樣的。
百分比高度
<div style="height: 6em;">
<p style="height: 50%;">Half as tall</p>
</div>
其中p的高度就是3em。由于top和bottom的margin如果設(shè)置為auto都會被強制設(shè)置為0,因此想要垂直居中的方式就是各設(shè)置為25%。但萬一父元素的高度沒有設(shè)置,那么這個百分比高度就會被重置為auto:
<div style="height: auto;">
<p style="height: 50%;">NOT half as tall; height reset to auto</p>
</div>

垂直方向margin的合并
合并(可能翻譯不太準(zhǔn)確,英文為collapsing), 只會發(fā)生在margin中,而不會在padding和border中。
舉個例子:
li {margin-top: 10px; margin-bottom: 15px;}
在一串li中設(shè)置每個的上下margin間距,一般是不是都會以為每個li的間距應(yīng)該是25px,而實際上結(jié)果卻是15px。這是因為發(fā)生了合并,在兩個相鄰的margin中會選取其中大的那個。

再來看個例子:
ul {margin-bottom: 15px;}
li {margin-top: 10px; margin-bottom: 20px;}
h1 {margin-top: 28px;}
從上面可知,這次li和h1的實際間隔其實是28px

負(fù)的margin和合并collapsing

來看另一個例子:
p.neg {margin-top: -50px; margin-right: 10px; margin-left: 10px; margin-bottom: 0; border: 3px solid gray;}
<div style="width: 420px; background-color: silver; padding: 10px; margin-top: 50px; border: 1px solid;">
<p class="neg"> A paragraph.
</p>
A div.
</div>

再來看個margin為負(fù)的例子:
p.neg {margin-bottom: -50px; margin-right: 10px; margin-left: 10px; margin-top: 0;
border: 3px solid gray;}
<div style="width: 420px; margin-top: 50px;"> <p class="neg">
A paragraph.
</p> </div>
<p>
</p>

List Item
我們常用的list item本身其實是有不少特殊的顯示規(guī)則的,比如marker,圓點和數(shù)字,而且這些都不在內(nèi)容區(qū)域中,所以上面所說的一系列css規(guī)則在他們身上也是通用的:

內(nèi)聯(lián)元素
介紹完了block元素就可以來看看inline元素了,常見的有em和a標(biāo)簽,這兩個都是不可替代的,而像image則是可替代的元素。注意我們后面討論的內(nèi)容不適用于table元素,在CSS2中引入了很多新的屬性和行為來出來表單元素的顯示,而這遠(yuǎn)遠(yuǎn)超過了本書的范圍。
可替換和不可替換的元素在inline模式下CSS的處理是有所區(qū)別的。
Line Layout 線框
首先我們要確定的就是inline的內(nèi)容是怎么放的,因為這不像block元素那么簡單的創(chuàng)建個block 盒子,而且其他元素是不能共存在同一塊區(qū)域的。那人們自然會很好奇,inline元素在block元素中是如何展示及修改樣式的。
為了了解線是怎么生成的,首先來看下單行長文本的情況,如下圖所示:
span {border: 1px dashed black;}

我們給span元素設(shè)置了一個簡單的虛線邊框。
基本概念
在我們進一步深入之前,讓我們來回顧下線框中用到的基本概念:
- 匿名文本:
這個主要指的是沒有包含在inline元素里面的內(nèi)容,比如
<p> I'm <em>so</em> happy!</p>
其中的i'm happy!就是匿名文本,注意空格也算是字符。
Em box
這是在給定字體中定義的,或者稱為字符box。在CSS中期em box的高度由font-size來定義。leading
leading其實是font-size和line-height的差。一般來說,這個差值將會一分為二,均勻分布在內(nèi)容區(qū)域的top和bottom部分。leading只會應(yīng)用在不可替換的元素上面。
inline 不可替換元素
接下來我們就來一步步 看下inline元素的創(chuàng)建過程,首先引入的是font-size的大小來決定內(nèi)容區(qū)域的高度。如果一個元素的font-size為15px的話,那么內(nèi)容區(qū)域的高度就是15px,如下圖所示:

接下來需要考慮的就是元素的行高,同時注意與font-size的高度差。如果元素的font-size=15px而line-height=21px那么這個差就是6px,接下來瀏覽器會將這6px均分到上下兩部分,如下圖所示:

讓我們假定下面是沒問題的:
<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasized</strong> and which is<br>
larger than the surrounding text.
</p>
可以看到,大多數(shù)文本的字體大小為12px,而strong的行高為24px,但由于行高是繼承的,所以strong繼承的行高為12px。對于strong來說line-height與font-size的差為-12px,所以上下都有-6px的分配,所以strong所代表的inline box是要小于內(nèi)容區(qū)域的,最后的效果如下圖所示。


從上圖看出,中間那行會比其他兩行高,但卻沒有完全包裹全部的文本內(nèi)容。
垂直對齊
如果我們改變inline box的垂直對齊方式,高度計算的原理還是一樣的:
<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br> which is <strong style="font-size: 24px; vertical-align: 4px;">strongly emphasized</strong> and that is<br>
larger than the surrounding text.
</p>
上面的變化同時抬升了storng元素的內(nèi)容區(qū)域和inline box,因為strong元素決定這inline box的上邊界,因此這一改變會同時抬升inline box的上邊界:

讓我們考慮另一種情況:
<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>,<br>
plus other text that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top;">tall</span> and is<br> larger than the surrounding text.
</p>

雖然樣式跟之前是一樣的,但又沒有發(fā)現(xiàn)tall文本的高度對齊到了inline box的上邊界。因為tall的文本具有相同的fontsize和line-height。
再來看下:
<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>,<br>
plus other text that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top; line-height: 2px;">tall</span> and is<br> larger than the surrounding text.
</p>
由于tall文本的行高小魚font-size,tall文本的inline box小魚內(nèi)容其余,這一小小的改變改變了tall文本的高度:

再來看看設(shè)置tall行高高于font-size的情況:
<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br> that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top; line-height: 18px;">tall</span> and that is<br>
larger than the surrounding text.
</p>
最后結(jié)合本章內(nèi)容總結(jié)下vertical-align的關(guān)鍵詞:
- top:對齊元素inline box的上邊界
- bottom: 對齊元素inline box的下邊界
- text-top: 對齊父元素文本上邊界
- text-bottom: 對齊父元素文本下邊界
- middle: 對齊元素inline box的垂直中點
- super: 上移內(nèi)容區(qū)域和inline box,具體依賴瀏覽器實現(xiàn)
- sub:跟super一樣,下移內(nèi)容
- <百分比>:基于line-height的百分比來移動
管理行高
為了防止重疊,我們建議使用em單位:
p {line-height: 1em;}
big {font-size: 250%; line-height: 1em;}
<p>
Not only does this paragraph have "normal" text, but it also<br> contains a line in which <big>some big text</big> is found.<br> This large text helps illustrate our point.
</p>
通過設(shè)置big的行高,我們增大了對應(yīng)inline box,而且不用改變所有行的行高。同時1em正好對應(yīng)字體的大?。?/p>

設(shè)置行高的比例因子
最好的設(shè)置行高的方式就是設(shè)置純數(shù)字,可以被所有的元素所繼承:
body {line-height: 1.5;}
p {font-size: 15px; line-height: 1.5;} small {font-size: 66%;}
big {font-size: 200%;}
<p>This paragraph has a line-height of 1.5 times its font-size. In addition, any elements within it <small>such as this small element</small> also haveline-heights 1.5 times their font-size...and that includes <big>this big element right here</big>. By using a scaling factor, line-heights scale to match the font-size of any element.</p>
p {font-size: 15px; line-height: 1.5;} small {font-size: 66%;}
big {font-size: 200%; line-height: 1em;}

添加box 屬性
想看看為inline 元素添加border的效果么:

inline元素的border屬性其實是由font-size控制的,而不是line-height。 換句話說,如果一個span元素的font-size為12px然后行高為36px,那么其實際的內(nèi)容區(qū)域的高度還是12px。但是border還是會包裹在inline元素周圍的.
接下來來看看padding屬性,padding的添加并不會改變內(nèi)容的高度
span {padding: 4px;}

改變breaking 行為
在上一節(jié),我們看到了一個inline不可替代元素跨行顯示的例子,它本身其實是單行的文本,每個字符被單獨的box包圍,行末被單獨分割。這種默認(rèn)的行為可以通過box-decoration-break來修改:

就兩個值:slice和clone。slice就是之前的默認(rèn)值。而對于clone,其主要特定為將每個片段都作為獨立得box,那這個獨立是什么意思呢?來看個例子:

發(fā)現(xiàn)在下方的clone選項里面,背景圖案不依賴文本獨立克隆了背景信息。box-decoration-break屬性雖然大多用在inline box中,不過其實它適用于任何的存在break的元素中。
inline 可替換元素
說到可替換元素,直接想到的就是img了。如下圖所示:

p {font-size: 15px; line-height: 18px;}
img {height: 30px; margin: 0; padding: 0; border: none;}
可替換元素最大的特點就是就有自己的固有高度和寬度,而這是會影響布局的,在inline 元素中嵌入img,那么整行的line box就會被img的高度給撐開(假如img固有高度大于line-height)。上圖中,由于圖片沒有margin,padding,border,因此inline box的高度就等于img的content 高度,也就是30px。
p {font-size: 15px; line-height: 18px;}
img {vertical-align: 50%;}
<p>the image in this sentence <img src="test.gif" alt="test image"> will be raised 9 pixels.</p>
替代元素和baseline
從上面的討論中可以明白inline的可替換元素默認(rèn)情況下是沿著baseline來排布的。如果元素有底部的padding,margin或是border的話,那么內(nèi)容區(qū)域也會相應(yīng)的抬升。而由于替代元素自己是沒有baseline的,所以對齊的就是inline box的baseline:

inline-block 元素
inline-block是在CSS2.1引入的聚合屬性,融合了inline和block。那這個inline-block到底是什么意思呢?其核心就表現(xiàn)為對外顯示inline屬性,就像image一樣,而對內(nèi)則表現(xiàn)出block屬性,可以設(shè)置相應(yīng)的width和height屬性。讓我們來看個具體的例子:
<div id="one">
This text is the content of a block-level level element. Within this block-level element is another block-level element. <p>Look, it's a block-level paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>
<div id="two">
This text is the content of a block-level level element. Within this block-level element is an inline element. <p>Look, it's an inline paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>
<div id="three">
This text is the content of a block-level level element. Within this block-level element is an inline-block element. <p>Look, it's an inline-block paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>
然后在上面的三個div分別應(yīng)用下面的樣式:
div {margin: 1em 0; border: 1px solid;}
p {border: 1px dotted;}
div#one p {display: block; width: 6em; text-align: center;}
div#two p {display: inline; width: 6em; text-align: center;} div#three p {display: inline-block; width: 6em; text-align: center;}

再舉個具體的使用例子,比如在導(dǎo)航欄中有5個鏈接,一邊想要他們inline顯示,而同時要設(shè)置他們的寬度為父元素的20%,name就可以使用inline-block來設(shè)置:
nav a {display: inline-block; width: 20%;}
Flow 顯示
flow和flow-root的屬性還是值得一說的。其實CSS的顯示分為兩個類型:外部類型和內(nèi)部類型。像之前說的inline,block都是控制外部類型顯示的,而flow就表示內(nèi)部元素的排列方式。
display:flow默認(rèn)會同時綁定block和inline的外部框架,如果沒有定義inline,name就以block形式展示:
#first {display: flow;}
#second {display: block flow;}
#third {display: inline flow;}
上面的例子中first和second顯示結(jié)果為block,而third則表現(xiàn)為inline box。
所以一般在display后面的參數(shù)中,第一個表示外部類型,而第二個表示內(nèi)部類型。比如:display: inline table。含義就是在外面以inline形式表示,而對內(nèi)則生成一個table的上下文。
說完了flow,下面看看flow-root,這個值和flow不一樣,默認(rèn)就是block類型的。這個值一般是針對root 元素的,一般就是html。
最后來看下display 新老值得對應(yīng)關(guān)系:
| 老的值 | 新的值 |
|---|---|
| block | block flow |
| inline | inline flow |
| inline-block | inline flow-root |
| list-item | list-item inline flow |
| table | block table |
| inline-table | inline table |
| flex | block flex |
| inline-flex | inline flex |
| grid | block grid |
| inline-grid | inline grid |
內(nèi)容顯示
另一個新增的display值就是contents,這個值的用處試講元素從頁面格式中去除。來看個例子:
ul {border: 1px solid red;}
li {border: 1px solid silver;}
<ul>
<li>The first list item.</li>
<li>List Item II: The Listening.</li>
<li>List item the third.</li>
</ul>
上面的例子會渲染一個帶紅色白框的ul list,而如果我們在ul上加上display:contents,name瀏覽器在渲染的時候就會像忽略外框的ul一樣,如圖所示:

其他的display值
在本章中我們其實還有很多display的值沒有被覆蓋到,大多數(shù)都是跟ruby相關(guān)的值,直到2018年底也沒得到很好的支持。
計算得出的值
如果元素設(shè)置為float或是position,那么計算出的display值也可能發(fā)生變化。事實上,display,position和float這三個值是有制約關(guān)系的。
如果一個元素的position設(shè)置為absolute,那么float就被自動設(shè)為none。而對于浮動和絕對定位的元素,其計算值由下表給出:

小結(jié)
雖然CSS的format模型在有些地方是反直覺的,但隨著使用的深入會慢慢理解它們。block元素是比較好理解的,而inline元素相比較就會顯得比較麻煩。