- 原文地址:Implementing a Mockup: CSS Layout Step by Step
- 原文做者:Dave Ceddia
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Baddyo
- 校對者:cyz980908,Moonliujk
對不少人來講,建立佈局是前端開發領域中最難啃的骨頭之一。php
你確定經歷過耗費數個小時,換着花樣地嘗試全部可能起做用的 CSS 屬性、一遍遍地從 Stack Overflow 上覆制粘貼代碼,寄但願於誤打誤撞地賭中那個能實現預期效果的魔幻組合。css
若是你的慣用策略就是循序漸進地組合佈局 —— 先把 A 元素放在這兒,好了,A 元素就位了,我再看怎麼把 B 放在那兒 …… 那你沒有挫敗感纔怪呢。CSS 的玩法可與 SKetch 或者 Photoshop 的玩法不同。html
在本文中,我將向你展現如何以統籌全局的思惟實現 CSS 佈局,根治佈局難產的頑疾。前端
咱們將用一個小案例貫穿全文,我會把全部的 CSS 代碼都解釋給你聽,所以即便你不知道或者忘記了 position
和 display
的用法,即便你分不清 align-items
和 justify-content
的區別,你仍會有所斬獲。react
並且咱們會用純 HTML 和 CSS 代碼來演示,所以你不須要 React、Vue、Angular、CSS-in-JS 甚至是 JavaScript 方面的知識儲備。android
聽起來很棒吧?那就開始吧。ios
在本文中,咱們要比照 Twitter 的推文組件本身仿寫一個:git
不管是一個像這樣的草圖,仍是一個細節精美的原型圖,「有章可循」 老是個好主意。github
要避免一邊在腦海裏設計,一邊在瀏覽器中七拼八湊地攢佈局,這樣的開發過程纔會更順暢。你固然能夠達到那種手腦合一的境界!但鑑於你還在乖乖地讀這篇文章,我能夠假設你尚未那麼神通廣大。:)後端
在動手敲代碼以前,咱們先把佈局的各個單元區分開來:
在用 CSS 鋪排佈局時,用行和列的形式去構思大有裨益。所以,要麼你把元素從上到下排列,要麼從左到右排列。這種行和列的思路完美對應了 CSS 中兩種佈局技術:Flexbox 和 Grid。
固然了,咱們的示例佈局並非中規中矩的行列。它有一張圖片鑲嵌在左側,其餘元素排列在右側。
畫一些方框把這些元素框起來,看看行和列是否初具規模。咱們把方向一致的單元歸到同一個方框中。
在頁面中的 HTML 元素基本上均可視爲矩形。固然,有些元素有圓角,有些元素是圓形,或者是複雜的 SVG 形狀等。一般你看不到頁面上有一堆矩形。但你能夠用矩形邊框的模式去分析它們。這樣的想象能幫你理解佈局。
之因此提到矩形,是由於你要把一系列元素對齊 —— 如第一行的用戶名、@handle(譯者注:handle 屬於專有名詞,指 Twitter 中的用戶 ID,因此在本文中保留不譯。詳見 www.urbandictionary.com/define.php?…)和時間以及最後一行的圖標 —— 把它們用方框包起來便於規劃。
按目前的規劃,把佈局用 HTML 代碼實現出來大概以下所示:
<article>
<img src="http://www.gravatar.com/avatar" alt="Name" />
<div>
<span>@handle</span>
<span>Name</span>
<span>3h ago</span>
</div>
<p>
Some insightful message.
</p>
<ul>
<li><button>Reply</button></li>
<li><button>Retweet</button></li>
<li><button>Like</button></li>
<li><button>...</button></li>
</ul>
</article>
複製代碼
展現出的效果是這樣的(能夠點擊這裏調試代碼):
這離咱們想要的效果還遠呢。可是!全部所需的內容都齊全了。有些元素還以從左到右的順序排列。
咱們能夠認爲,即便不用進一步設置樣式,目前的佈局效果也能達到網頁想表達的要點,這也是一個優秀的 HTML 應該達到檢查標準。
你可能會好奇,爲什麼我選的是那些元素 —— article
、p
等等。爲什麼不都用 div
呢?
爲什麼要這樣寫:
<article>
<img ... />
<div>
<span/>
<span/>
<span/>
</div>
<p> ... </p>
<ul>
<li>
<button> ... </button>
</li>
</ul>
</article>
複製代碼
而不這樣寫?
<div>
<img ... />
<div>
<div/>
<div/>
<div/>
</div>
<div> ... </div>
<div>
<button> ... </button>
</div>
</div>
複製代碼
其實,每一個 HTML 元素的名稱都有其特定含義,在不一樣場景中恰如其分地使用語義上與它們所表示的內容匹配的元素,是很好的語義化實踐。
這種寫法,首先,有助於開發者理解代碼;其次,對使用屏幕閱讀器等輔助設備的用戶比較友好。同時這樣用標籤也有利於 SEO —— 搜索引擎會試着理解這個頁面的含義,以便於顯示相關廣告來盈利、幫助搜索者找到滿意結果。
article
標籤表明文章類內容,而你能夠認爲推文這種東西有點相似於一篇文章。
p
標籤表明段落,而推文的內容文本有點相似於一個段落。
ul
標籤表明無序列表(與有序列表或數字序號列表相對應),在本示例中,你能夠用它來存放列表信息。
咱們沒法用隻言片語就說清楚 HTML 元素的語義,以及何種狀況用何種標籤。但大多數狀況下,一個語義化元素即便其語義再不貼切,也比用 div
強,div
標籤只表明 「一塊區域」。
是什麼決定了元素的樣式?爲何有的元素獨佔一行,而有的元素能共處一行?
這要歸因於元素的默認樣式,這其中就有咱們要探討的第一個 CSS 知識點:行內元素和塊級元素。
行內元素們肩並肩擠在一行裏(就像句子中的詞同樣,必要時會折行)。根據再瀏覽器中的默認樣式劃分,span
、button
以及 img
都是行內元素。
而塊級元素,老是踽踽獨行。以控制檯輸出的方式去理解,你能夠認爲塊級元素先後各有一個換行符 \n
。就好像console.log("\ndiv\n")
。article
、div
、li
、ul
以及 p
標籤都是塊級元素。
注意,在上面的例子中,爲何即便 img
標籤是行內元素,頭像圖片依然獨佔一行?由於它下方的 div
是塊級元素。
而後要注意,爲何 @handle、用戶名和時間都在同一行?緣由是它們都在 span
標籤中,而 span
是行內元素。
這三個 span
和 文字 「insightful message」 處於不一樣行,由於(a)它們被包在一個 div
中,div
後面天然要另起一行;(b)p
標籤一樣是塊級元素,它天然重新行開始排列。(之全部沒有出現兩個空行,是由於 HTML 合併了相鄰的空行,與相鄰空格同理。)
若是你再看得仔細點,你會發現 「insightful message」 的上下方空間,要比頭像圖片以及 handle、用戶名、時間的上下方空間要大。此空間的大小也由默認樣式控制:p
標籤的頂部和底部都有 margin。
你也會注意到按鈕列表的圓點,以及列表的縮進行爲。這些也都是默認樣式。咱們立刻就要修改這些默認樣式了。
咱們想把頭像圖片放在左側,其他元素放在右側。你可能會根據剛剛探討的行內和塊級知識來推斷,認爲只要把右側的元素都包裹到一個如 span
標籤般的行內元素中,就完事大吉了。
但這是行不通的。行內元素並不能阻止其內部的塊級元素另起一行。
爲了把這些元素收拾得服服帖帖,咱們須要用一些更強大的技術,好比 Flexbox 或者 Grid 佈局。此次咱們選用 Flexbox 來解決。
CSS 的 Flex 佈局可以把元素以行或者列的形式排布。這是一種單向的佈局系統。爲了實現交叉的行和列(正如推文組件的設計那樣),咱們須要添加一些容器元素來扭轉方向。
你能夠在容器上設置 display: flex;
來啓用 Flex 佈局。容器自己是塊級元素(得以獨佔一行),其內部元素會成爲 「Flex 子項」 —— 即它們再也不是行內或塊級元素了;它們都受 Flex 容器控制。
在本例中,咱們會設置一些嵌套的 Flex 容器,讓該成行的成行,該成列的成列。
咱們把外層容器(綠色方框)設置爲列,藍色方框設置爲行,而紅色方框中的元素排布在列中。
因爲一些緣由,我決定用 Flexbox 佈局而不用 Grid 佈局。我以爲 Flexbox 佈局更易於學習,也更適用於輕量級的佈局。當佈局中主要是行或者主要是列時,Flexbox 佈局的表現更出色。
另外一個重點就是,即便 Grid 佈局比 Flexbox 佈局年輕,前者也撼動不了後者的地位。它們各自適用於不一樣的場景,對於兩者,咱們都要學習,技不壓身。有些狀況你甚至會同時使用兩者 —— 例如 Grid 佈局排布總體頁面,而 Flexbox 佈局調控頁面中的一個表單。
沒錯沒錯,在 Web 開發的世界,廣泛的更替法則是後浪推前浪,但 CSS 並不如此。Flexbox 和 Grid 可以和諧共存。
用 CSS 解決問題,條條大路通羅馬!
好了,既然咱們已經打定主意,那就開動吧。我把左側元素包進一個 div
,並給元素們設置類名,便於應用 CSS 選擇器。
<article class="tweet">
<img class="avatar" src="http://www.gravatar.com/avatar" alt="Name" />
<div class="content">
<div class="author-meta">
<span class="handle">@handle</span>
<span class="name">Name</span>
<span class="time">3h ago</span>
</div>
<p>
Some insightful message.
</p>
<ul class="actions">
<li><button>Reply</button></li>
<li><button>Retweet</button></li>
<li><button>Like</button></li>
<li><button>...</button></li>
</ul>
</div>
</article>
複製代碼
(代碼在這裏)
看着好像沒有變化。
這是由於 div
做爲塊級元素(若是沒有空行就引入一個)是看不見的。當你須要一個包裹其餘元素的容器,除了 div
以外沒有更貼合語義的選擇了。
下面我們的第一段 CSS 代碼,咱們會把它放在 HTML 文檔中 head
標籤的 style
裏:
.tweet {
display: flex;
}
複製代碼
幹得漂亮!咱們用類選擇器鎖定了全部類名爲 tweet
的元素。固然目前只有一個這樣的元素,但若是有十個,那它們將都會是 Flex 容器了。
CSS 中以 .
開頭的選擇器表明類選擇器。爲何是 .
?我可不知道。你只要記住這條規則就好了。
如今文字內容都到頭像右側去了。問題是頭像圖片都扭曲變形了。
由於 Flex 容器會默認:
咱們能夠用 align-items
屬性來控制垂直方向的對齊方式。
.tweet {
display: flex;
align-items: flex-start;
}
複製代碼
align-items
的默認值是 stretch
,而將其設爲 flex-start
後,會讓子項沿着容器頂部對齊,而且讓子項保持各自的高度。
另外,Flex 容器的默認排列方向是 flex-direction: row;
。是的,這個方向是 「行」,即便咱們可能感受那更像是兩列。要把它想成是子項們排成一行,這樣理解就舒服多了。
有點像這張花瓶的圖片,或者說兩張臉的圖片。橫當作嶺側成峯。
Flex 佈局的子項僅取其所需寬度,但咱們須要 content
區域儘可能寬敞一些。
所以,咱們要給 content
這個 div 設置 flex: 1;
屬性。(該 div 有類名,那咱們就又能夠用類選擇器啦!)
.content {
flex: 1;
}
複製代碼
咱們也要給頭像設置 margin
,好在頭像和文字之間加點空隙:
.avatar {
margin-right: 10px;
}
複製代碼
看起來順眼一些了吧!
那…… 爲何用 margin
而不用 padding
?爲何要設置在頭像右側,而不是文字內容左側呢?
這是一條約定俗成的規則:在元素右側和下方設置 margin,不去碰左側和上方的 margin。
至少是在英文界面的佈局中,文檔流的方向是從左到右、從上到下的,所以,每一個元素都 「依賴」 其左側和上方的元素。
在 CSS 中,每一個元素的定位都受到其左側和上方的元素的影響。(至少在你碰見 position: absolute
那幫傢伙以前是這樣的。)
從技術實現的角度來講,怎樣設置 avatar
和 content
之間的空隙都同樣。該是多寬就是多寬,沒有 border
的干擾(padding
在 border
的內側;而 margin
在外側)。
但當事關可維護性、對元素的全局觀時,這就有區別了。
我曾嘗試把元素理解爲一個個獨立個體,就像每一個 JavaScript 函數只實現單一功能同樣:若是它們都僅僅扮演單一的角色,那麼寫起代碼來就很容易,報錯時調試也很容易。
若是咱們把 margin 設置到 content
的左側,後來有一天咱們去掉了 avatar
,但是之前的縫隙還留在那。咱們還得排查致使額外空間的緣由(是來自 tweet
容器嗎? 仍是來自 content
呢?)並把它處理掉。
或者,若是 content
設置了左側的 margin,而咱們想要把 content
替換成別的元素,咱們還要記着再把以前那個空隙補上。
好了好了,爲了 10 像素的事,不必費這麼多口舌,乾脆就把 margin 設在頭像的右側和下方。讓咱們繼續埋頭敲代碼吧。
無序列表 ul
和其中的列表項 li
在左側窩藏了很大空間,還有一些圓點。這都不是咱們想要的效果。
咱們能夠把無序列表左側的空隙都清除掉。咱們還要把它變成一個 Flex 容器,這樣裏面的按鈕就能排成一行了(用 flex-direction: row
)。
列表項有個屬性是 list-style-type
,默認值爲 disc
,使得每一個列表項以圓點開頭,咱們用 list-style: none;
(list-style
是一個縮寫屬性,整合了幾個其餘屬性,其中就包括 list-style-type
)將該效果關閉。
.actions {
display: flex;
padding: 0;
}
.actions li {
list-style: none;
}
複製代碼
.actions
又是一個類選擇器。原汁原味。
而 .actions li
選擇器,意即 「actions
類元素中全部的 li
元素」。它是類選擇器和元素選擇器的結合。
複合選擇器中用以分隔的空格表明着選擇範圍的縮小。事實上,CSS 是以倒序讀取選擇器的。其過程是 「先找到頁面中全部的 li
,而後在這些 li
中找到類名是 actions
的那些」。但不管你用正序仍是倒序的方式去理解,結果都是同樣的。(在 StackOverflow 查看更多詳解)
要橫排按鈕有好幾種方式。
一種就是設置 Flex 子項的對齊方式。你應該對設置對齊方式很熟悉,每一個富文本編輯器頂部都有這種功能的按鈕:
它們把文本進行左對齊、居中對齊、右對齊以及 「兩端對齊」,也就是鋪滿整行。
在 Flexbox 佈局中,你能夠用 justify-content
屬性來實現對齊。設置了 flex-direction: row
(默認值,也是本文中一直在用的設置)後,能夠經過 justify-content
把子項進行或左或右地對齊。justify-content
的默認值爲 flex-start
(所以全部元素都向左看齊)。若是咱們給 .actions
元素設置 justify-content: space-between
,它們就會均勻地鋪滿整行,就像這樣:
可咱們想要的不是這樣的效果。若是這幾個按鈕能夠不佔滿整行會更好。因此得換一種方式。
此次,咱們給每一個列表項設置一個右側的 margin,把它們分隔開來。還要給整個推文組件設置一個邊框,以便咱們可以直觀地衡量效果。用 1px solid #ccc
設置一個 1 像素寬的灰色實線邊框。
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
}
.actions li {
list-style: none;
margin-right: 30px;
}
複製代碼
如今效果以下:
按鈕的排列看起來優雅多了,但灰色邊框告訴咱們,全部元素都過於靠左了。仍是用 padding
分配點空間吧。
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
padding: 10px;
}
複製代碼
如今推文組件有內邊距了,但有些地方仍是很空。若是咱們用瀏覽器調試工具將元素高亮顯示,就會發現 p
和 ul
元素有默認的上下 margin(在 Chrome 的調試工具中,margin 以橙色顯示,而 padding 以綠色顯示):
還有一處有意思的細節;行與行之間的上下 margin 是等距的 —— 並無疊加出雙倍間距!由於 CSS 在豎直方向上有 margin 坍塌現象。當上下兩個 margin 短兵相接時,數值大的 margin 會 「吃掉」 小的。詳情參見 CSS 技巧:margin 坍塌。
對於本例的佈局,我會手動調整 .author-meta
、p
和 ul
的右側 margin。若是要真刀真槍地開發網站,建議你考慮用 CSS reset 做爲開發基礎,有利於跨瀏覽器兼容。
p, ul {
margin: 0;
}
.author-meta, p {
margin-bottom: 1em;
}
複製代碼
用 ,
將選擇器隔開,能夠一次性把樣式應用到多個選擇器上。所以 p , ul
的含義就是 「全部的 p
元素,以及全部的 ul
元素」。亦即兩者的合集。
在這裏咱們使用了新的尺寸單位,1em
中的 em
。一個單位的 em
等於 body
標籤上的以像素爲單位的字號大小。body
標籤的默認字號爲 16px
(16 像素高),因此本例中的 1em
至關於 16px
。em
隨字號改變而改變,所以能夠用 1em
來表達 「我想讓文字下方的 margin 和文字的高度同樣,不論文字高度是多少」。
如今的效果以下:
如今讓咱們把圖片縮小一些,並將其設置爲圓形。咱們將其寬高設置爲 48 像素,正和 Twitter 的頭像寬高同樣。
.avatar {
margin-right: 10px;
width: 48px;
border-radius: 50%;
}
複製代碼
咱們用 border-radius
屬性來設置圓角,有好幾種方式來定義該屬性的值。若是你想要小圓角效果,能夠用帶 px
、em
或其餘單位名稱的數字賦值。例如 border-radius: 5px
的效果:
若是將 border-radius
設爲寬和高的一半(在本例中即爲 24 像素),其效果就是一個圓形。但更方便的寫法是 border-radius:50%
,這樣咱們就沒必要知道具體尺寸,CSS 會計算出確切結果。甚至,若是之後寬高值變了,也無需從新修改屬性值了!
眼下還有一些須要潤色之處。
咱們要把字體設爲 Helvetica(Twitter 用的那一款)、把字號縮小一些、把用戶名加粗,還有,翻轉 「@handle 用戶名 的順序(在 HTML 代碼中),使之與 Twitter 如出一轍。:D
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
padding: 10px;
/* 更改字體和字號。 在 .tweet 選擇器上設置的 CSS 效果,其全部子元素都會繼承。 (除了按鈕。按鈕不太合羣) */
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
}
.name {
font-weight: 600;
}
.handle,
.time {
color: #657786;
}
複製代碼
font-weight: 600;
的效果等同於 font-weight: bold;
。字體有不少不一樣程度的字重,範圍是從 100 到 900(最淡到最濃)。normal
(默認值)等價於 400。
另外,CSS 中的註釋寫法與 JavaScript 或其餘語言不用,不容許以 //
開頭。某些瀏覽器支持 //
風格的 CSS 註釋,但並不是全部瀏覽器都如此。用 C 語言風格的 /* */
包圍註釋內容便可高枕無憂。
還有一個小竅門:能夠用 僞元素在 「handle」 與 「時間」 之間添加一個凸點。這個凸點符號單純爲了裝飾,不具備具體語義,因此用 CSS 實現不會污染 HTML 語義結構。
.handle::after {
content: " \00b7";
}
複製代碼
::after
建立了一個僞元素,它位於 .handle
元素內部的最後方(「落後」 於元素的內容)。你還能夠用 ::before
建立僞元素。能夠給 content
屬性賦值任何文字內容,包括 Unicode 字符。你能夠恣意發揮,像給任何其餘元素設置樣式同樣。僞元素用來實現標記(badge)、消息提醒或其餘小花樣最合適不過了。
還有一項工做要作,那就是用圖標替換按鈕。咱們要在 head
標籤裏添加 Font Awesome 圖標字體:
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" />
複製代碼
而後用下列代碼替換原來的 ul
,新列表中的每一個按鈕裏有圖標和隱藏文字:
<ul class="actions">
<li>
<button>
<i class="fas fa-reply" aria-hidden="true" ></i>
<span class="sr-only">Reply</span>
</button>
</li>
<li>
<button>
<i class="fas fa-retweet" aria-hidden="true" ></i>
<span class="sr-only">Retweet</span>
</button>
</li>
<li>
<button>
<i class="fas fa-heart" aria-hidden="true" ></i>
<span class="sr-only">Like</span>
</button>
</li>
<li>
<button>
<span aria-hidden="true">...</span>
<span class="sr-only">More Actions</span>
</button>
</li>
</ul>
複製代碼
Font Awesome 是一款圖標字體,它配合斜體標籤 i
能夠展現圖標。正由於它是字體,那些能夠用於文字的 CSS 屬性(例如 color
和 font-size
)都適用於圖標字體。
咱們在這兒作了些微調,來提高按鈕的可訪問性:
aria-hidden="true"
使屏幕閱讀器忽略此圖標。sr-only
類是 Font Awesome 內置的類。它讓元素在你眼前隱身,但屏幕閱讀器能讀取到它。這裏有一門由 Marcy Sutton 講授的關於圖標按鈕可訪問性的免費 Egghead 課程。
如今咱們將要給按鈕添加一些樣式 —— 移除邊框、上色以及加大字號。還要設置 cursor: pointer
,把鼠標光標變成 「手」 型,就像超連接的效果那樣。最後,用 .actions button:hover
選擇處於 hover 狀態的按鈕,把它們變成藍色。
.actions button {
border: none;
color: #657786;
font-size: 16px;
cursor: pointer;
}
.actions button:hover {
color: #1da1f2;
}
複製代碼
下面就是推文組件光芒四射的最終效果:
若是你想本身調試代碼,到沙箱裏來。
最能提升 CSS 水平的就是實踐。
仿寫你喜歡的網站。設計者和藝術家稱其爲 「臨摹」。我寫過一篇用臨摹的方法學 React,其中的原則也適用於 CSS。
選一些有意思的、你以爲難度大的樣式效果。用 HTML 和 CSS 臨摹該效果。若是卡殼了,用瀏覽器的調試工具看看原網站的效果是如何實現的。「栽秧苗、腿跟上、擡頭看看直不直。」 :)
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。