《CSS 世界》讀書筆記 - 流與寬高
1. 前言
在學習 CSS 的過程中,我經常會被數不清的屬性和特性弄得暈頭轉向。作爲前端新手,經常會坐在顯示器前花很多很多時間去 “追” 視覺稿,也經常會在 “爲什麼這個屬性不生效” 和 “爲什麼這個屬性生效了但是不是我想要的效果” 之間搖擺,直到我開始看張鑫旭老師的《CSS 世界》,纔開始漸漸地走進 CSS 世界,才明白原來 CSS 的背後也是有一套 “物理” 和 “魔法” 法則。遵循着法則,很多問題也許會迎刃而解。
因爲在閱讀本書 CSS 的 “流” 相關內容時讓我有了一種恍然大悟的感覺,所以纔有了此篇讀書筆記。首先,用張鑫旭老師在本書開篇寫下的一句話送給大家:
“挖掘簡單現象背後的原因,會讓你學到很多別人很難學到的 CSS 技能和知識。”
2. 流
CSS 中,有一個隱形的基本定位和佈局機制,那便是 “流”。
在網絡上,隨便搜索一篇 CSS 教程,基本都會提到 “普通流” 和 “文檔流”,還有 “文本流” 這些關鍵詞,有時候經常會弄混淆他們。這裏我專門去查了一下才發現了他們之間的差異。所謂的文檔流,實際就是普通流,在 W3C 的規範中並沒有 “document flow”,只有 “nomal flow”,之所以出現普通流和文檔流,很可能是早期對英文文檔的不同翻譯而造成的混淆。而從 W3C 的中對 normal flow 的介紹中,也可以看出,普通流是用來針對於盒模型來說的。而 “文本流” 是針對元素內部文字(符)的排列來說的。兩者都是 “流”,只是描述的對象不同。
“流” 跟現實世界的 “水流” 有異曲同工的表現。所謂 “流”,就是 CSS 世界中引導元素排列和定位的一條看不見的 “水流”。
對比水流和 CSS 文本流:
<div>
作爲塊級元素就像是鋪滿容器的水,注意是鋪滿;而<span>
作爲內聯元素就像是漂浮在水中的木頭。如果沒有人爲的干預(比如魔鬼 float),元素總是會按照既定的流(塊級元素自上而下,行內元素從左到右),有順序有組織地排列。
2.1 流體佈局
既然流是佈局的機制,那麼利用流的機制和特性就可以實現流體佈局。使用流體佈局從一定程度上可以幫助我們編寫精簡且巧妙的 CSS ,保持佈局的強擴展性和韌性。
2.2 塊級元素和內聯元素
塊級元素:block-level element
常見的塊級元素有 <div>
、<li>
、<table>
、<p>
,、<h1>
- <h6>
等,需要注意是,“塊級元素” 和 “display: block” 不是一個概念。這裏需要注意一下塊級元素的基本特徵:一個水平流上只能單獨顯示一個元素,多個塊級元素則換行顯示。
除此之外,塊級元素還有可以控制高度、行高、以及寬度默認爲包含該塊級容器的 100%。而在這些列舉的塊級元素中 <li>
元素默認的 display 值是 list-item
,<table>
元素默認的 display 值是 table
,但是它們均是 “塊級元素”,因爲它們都符合了塊級元素的基本特徵。
下面就來仔細的看看上面提到的 “display: block”、“display:list-item”、“display: table”:
display: block
如果不指定寬高,默認會繼承父元素的寬度,並且獨佔一行,即使寬度有剩餘也會獨佔一行。例子中,寬度繼承於父元素 body。
高度一般以子元素撐開的高度爲準,當然也可以自己設置寬度和高度。例子中高度被兩個
<p>
子元素撐開。
display: list-item
默認會把元素作爲列表顯示,要完全模仿列表的話還需要加上 list-style-position,list-style-type。“盒子” 魔術:爲什麼 list-item 元素會出現項目符號?所有的 “塊級元素” 都有一個 “主塊級盒子”,list-item 除此之外還有一個 “附加盒子”。list-item 元素會出現項目符號是因爲生成了一個附加的盒子,學名 “標記盒子”(marker box),專門用來放圓點、數字這些項目符號。
display: table
作爲塊級表格來顯示(類似 table),表格前後帶有換行符。使用基於表格的 CSS 佈局,使我們能夠輕鬆定義一個單元格的邊界、背景等樣式, 而不會產生因爲使用了table
那樣的製表標籤所導致的語義化問題。
正是由於 “塊級元素” 具有換行特性,因此理論上它都可以配合 clear 屬性來清除浮動帶來的影響。🌰 點擊
內聯元素:inline element
與塊級元素負責結構不同,內聯元素負責內容。比如 <a>
、<span>
、<i>
都是常見的內聯元素。內聯元素最大的特點就是:可以和文字在一行顯示,除此之外,它的高,行高及外邊距和內邊距不可改變。
穿着 inline 的皮藏着 block 的心
每個元素都有兩個盒子,外在盒子和內在盒子。外在盒子負責讓元素可以一行顯示,還是隻能換行顯示;內在盒子負責寬高、內容呈現什麼的,也叫容器盒子。
按照 display 的屬性值不同,值爲 block 的元素的盒子實際由 外在的 “塊級盒子” 和 內在的 “塊級容器盒子” 組成,值爲 inline-block 的元素則由 外在的 “內聯盒子” 和 內在的 “塊級容器盒子” 組成,值爲 inline 的元素則內外均是 “內聯盒子”。
這裏比較抽象,注意不要混淆了內聯盒子和容器盒子(內在盒子)的概念。
3. 流與 width/height
在理清了流、塊級元素和內聯元素後,再去理解元素的寬高就會有不一樣的感悟。width/height 作用在內在盒子,也就是 “容器盒子”。
3.1 width: auto
寬的默認值是 auto,至少包含了以下 4 種不同的寬度表現:
(1)充分利用可用空間,fill-available。比如像 <div>
這樣的塊級元素,它們的寬度默認是包含與它們的父級容器寬度的 100%。
(2)收縮與包裹,fit-content。指的是父元素的寬度會收縮到和內部元素寬度一樣。比如浮動元素,position 爲 abosolute/fixed,inline-block 等。
以 float 元素爲例子:
.div-1 {
float: left;
padding: 10px;
border: 2px solid black;
}
.div-2 {
width: 200px;
height: 200px;
border: 2px solid red;
background-color: aqua;
}
(3)收縮到最小,min-content,指的是內部元素最大的最小寬度,如 table-layout 爲 auto 的表格。
(4)超出容器限制。內容超出了父容器,如果明確設定 width 或者內聯元素開啓了 white-space: nowrap 屬性,一般都不會出現這個情況。
3.2 width: 100%,失去流動性的寬度
早前,我也比較喜歡給子元素設定 width: 100%,以爲這樣子元素就可以佔滿父元素,然而事實真的如此嗎?下面有一個例子:
尺寸超出的原因是,在標準的盒子模型下,元素的寬度 = 內容寬度 + 內邊距 + 邊框寬度。
給子元素 <a>
標籤設置了 width: 100%,此時的 內容寬度 已經等於外元素的寬度,所以超出的尺寸是設置的 margin 和 padding。
去掉 margin 和 padding 後(其實這裏改變 box-sizing 從 content-box 到 border-box 也可以解決):
所以,width: 100% 就不像 “水流” 那樣完全利用容器空間,即所謂的 “流動性丟失”。
3.3 width 值作用的細節
當我們給一個 <div>
元素設定寬度的時候,這個寬度是如何作用到它上面的呢?比如在 div { width: 100px; }
中 100px 的寬度是如何作用到這個 <div>
元素上的。
要回答這個問題首先需要了解一下外在盒子和內在盒子(也稱爲容器盒子)。之前討論的塊級元素和內聯元素,當我們在談論它們是在一行還是換行顯示時,實際上是談論的外在盒子。而內在盒子實際是負責了元素的寬高和內容。
內在盒子由內到外又可分爲:content box、padding box、border box 和 margin box。
仔細地看,我們會發現 content box 是環繞着 content 給定寬高的矩形,所以 width: 100px 作用在了 content box 上。舉個例子:
div {
width: 100px;
padding: 20px;
border: 20px solid;
}
元素的寬度此時爲 180px = 20px (border-right) + 20px (padding-right) + 100px (content) + 20px (border-left) + 20px (padding-left)。
但這種寬設定卻讓流動性消失了,當我們給定元素設定 width: auto,元素的寬就會 “自適應” 地鋪滿容器,而給定了 width 值會讓這種流動性消失。所以 “無寬度” 準則會讓佈局更靈活、容錯性更高。從另一方面來說,如果沒有精準去計算 border、padding 和 content 的寬度,很容易因爲空間不足,導致頁面佈局錯位的問題。
3.4 寬度分離原則
“寬度分離原則”,就是 CSS 中的 width 屬性不與影響寬度的 padding/border(有時候包括 margin)屬性共存,也就是不能出現以下的組合:
.first-div {
width: 100px;
border: 1px solid;
}
/* 或者 */
div {
width: 100px;
padding: 20px;
}
bad case
假如現在我們想在第一個 div 上添加 padding,那麼加上 padding: 20px; 的屬性:
.first-div {
width: 100px;
border: 1px solid;
padding: 20px;
}
此時其實佈局已經發生了變化:
在未加上 padding 之前,這個 div 的寬高是 102px,加上 padding 後變成了 142px,比之前的大了 40px,顯然佈局很容易出問題。爲了不影響之前的佈局,我們還需要通過計算減去 40px 的 padding 大小才能和之前的大小保持一致:
.box {
width: 60px; /* 通過計算,減去 40 像素 */
padding: 20px;
border: 1px solid;
}
good case
如果我們使用了寬度分離,事情就會輕鬆很多:
/* 在first-div外嵌套一層 */
.first-div-father {
width: 102px;
}
.first-div {
border: 1px solid;
}
嵌套一層標籤,父元素定寬,子元素因爲 width 使用的是默認值 auto,所以會如水流般自動填滿父級容器。因此,子元素的 content box 寬度就是 100px,和上面直接設置 width 爲 100px 的表現一樣。
3.5 改變 width/height 作用細節的 box-sizing
box-sizing 屬性改變了 width 作用的盒子。“內在盒子” 的 4 個盒子分別是 content box、padding box、border box 和 margin box。默認情況下,width 是作用在 content box 上的,box-sizing 的作用就是可以把 width 作用的盒子變成其他幾個,因此,理論上,box-sizing 可以有下面這些寫法:
.box1 {box-sizing: content-box;} → 默認值
.box2 {box-sizing: padding-box;} → Firefox 曾經支持
.box3 {box-sizing: border-box;} → 全部支持
.box4 {box-sizing: margin-box;} →從未支持
爲何 box-sizing 不支持 margin-box
如果我們使用 width 或 height 設定好了尺寸,那請問,我們此時設置 margin 值,其 offset 尺寸會不會有變化。顯然不會,一個本身並不會改變元素尺寸的盒子,沒有讓 box-sizing 支持的道理。
box-sizing 發明的初衷
box-sizing 被髮明出來最大的初衷應該是解決替換元素寬度自適應問題,比如 textarea 和 input。
4. height
4.1 相對簡單而單純的 height: auto
height: auto 要比 width: auto 簡單而單純得多,原因在於,CSS 的默認流是水平方向的,寬度是稀缺的,高度是無限的。height: auto 的表現也基本上就是:有幾個元素盒子,每個多高,然後一加,就是最終的高度值了。
4.2 height: 100%
對於 height 屬性,如果父元素 height 爲 auto,只要子元素在文檔流中,其百分比值完全就被忽略了。只要經過一定的實踐,我們都會發現對於普通文檔流中的元素,百分比高度值要想起作用,其父級必須有一個可以生效的高度值。
4.3 爲何父級沒有具體高度值的時候,height: 100% 會無效呢?
一個錯誤的說法❌:死循環
例如,一個 <div>
有一個高度爲 100px 的子元素,此時,這個 <div>
被子元素撐起來後高度就是 100px,假設此時插入第二個子元素,高度設爲 height: 100%,那麼第二個子元素的高度就是 100px。但是, <div>
的 height 已經變成了 200px,而第二個子元素的高又會變成 200px。如此反覆形成了邏輯上的死循環,然而這種說法是錯誤的。
正確的解釋 ✅:瀏覽器的順序渲染
首先瀏覽器渲染的基本原理:先渲染父元素,後渲染子元素,是有先後順序的。
如果包含塊的高度沒有顯式指定(即高度由內容決定),並且該元素不是絕對定位,則計算值爲 auto,所以高度計算出來是 'auto' * 100 / 100 = NaN。
那如何讓元素支持 height: 100% 的效果呢?
-
設定顯式的高度值,比如設置 height: 600px,或者可以生效的百分比值高度;
-
使用絕對定位。
使用絕對定位時,需要注意絕對定位的寬高百分比計算是相對於 padding box 的,也就是說會把 padding 大小值計算在內,但是,非絕對定位元素則是相對於 content box 計算的。
5. 總結
在這篇筆記中,主要總結了流與寬高之間是如何相互影響的,同時還探索了部分盒模型的問題。希望能給大家在平常開發時,帶來一定的啓發。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/0YGg7KHm3UcjkzURWyQJng