02.28 從原型圖到成品:步步深入CSS佈局

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

每天凌晨00點00分, 第一時間與你相約


每日英文

Sometimes being too nice is dangerous, you have to show your mean side once in a while to avoid getting hurt.

有時候,太善良也是種危險。偶爾你也得厲害一點,才能免受傷害。


每日掏心話

做人不要太玻璃心,不要別人一條信息沒回,就覺得自己做錯了什麼,不要被人一句”呵呵“,就覺得對方是討厭自己。玻璃心,想太多,什麼事都對號入座,何必那麼累。


鏈接:juejin.im/post/5cebb52651882530be7b16a4

從原型圖到成品:步步深入CSS佈局

程序員小樂(ID:study_tech)第 789 次推文 圖片來自百度


往日回顧:30 張圖帶你徹底理解紅黑樹


正文


從原型圖到成品:步步深入CSS佈局

對很多人來說,創建佈局是前端開發領域中最難啃的骨頭之一。

你肯定經歷過耗費數個小時,換著花樣地嘗試所有可能起作用的 CSS 屬性、一遍遍地從 Stack Overflow 上覆制粘貼代碼,寄希望於誤打誤撞地賭中那個能實現預期效果的魔幻組合。

如果你的慣用策略就是按部就班地組合佈局 —— 先把 A 元素放在這兒,好了,A 元素就位了,我再看怎麼把 B 放在那兒 …… 那你沒有挫敗感才怪呢。CSS 的玩法可與 SKetch 或者 Photoshop 的玩法不一樣。

在本文中,我將向你展示如何以統籌全局的思維實現 CSS 佈局,根治佈局難產的頑疾。

我們將用一個小案例貫穿全文,我會把所有的 CSS 代碼都解釋給你聽,因此即使你不知道或者忘記了 position 和 display 的用法,即使你分不清 align-items 和 justify-content 的區別,你仍會有所斬獲。

而且我們會用純 HTML 和 CSS 代碼來演示,因此你不需要 React、Vue、Angular、CSS-in-JS 甚至是 JavaScript 方面的知識儲備。

聽起來很棒吧?那就開始吧。

佈局小例子

在本文中,我們要比照 Twitter 的推文組件自己仿寫一個:

從原型圖到成品:步步深入CSS佈局

不論是一個像這樣的草圖,還是一個細節精美的原型圖,“有章可循” 總是個好主意。

要避免一邊在腦海裡設計,一邊在瀏覽器中七拼八湊地攢佈局,這樣的開發過程才會更順暢。你當然可以達到那種手腦合一的境界!但鑑於你還在乖乖地讀這篇文章,我可以假設你還沒有那麼神通廣大。:)

第一步:分而治之

在動手敲代碼之前,我們先把佈局的各個單元區分開來:

從原型圖到成品:步步深入CSS佈局

在用 CSS 鋪排佈局時,用行和列的形式去構思大有裨益。因此,要麼你把元素從上到下排列,要麼從左到右排列。這種行和列的思路完美對應了 CSS 中兩種佈局技術:Flexbox 和 Grid。

當然了,我們的示例佈局並不是中規中矩的行列。它有一張圖片鑲嵌在左側,其他元素排列在右側。

第二步:沿著各個單元畫方框

畫一些方框把這些元素框起來,看看行和列是否初具規模。我們把方向一致的單元歸到同一個方框中。

從原型圖到成品:步步深入CSS佈局

在頁面中的 HTML 元素基本上都可視為矩形。當然,有些元素有圓角,有些元素是圓形,或者是複雜的 SVG 形狀等。通常你看不到頁面上有一堆矩形。但你可以用矩形邊框的模式去分析它們。這樣的想象能幫你理解佈局。

之所以提到矩形,是因為你要把一系列元素對齊 —— 如第一行的用戶名、@handle(譯者注:handle 屬於專有名詞,指 Twitter 中的用戶 ID,所以在本文中保留不譯。詳見 www.urbandictionary.com/define.php?…)和時間以及最後一行的圖標 —— 把它們用方框包起來便於規劃。

按目前的規劃,把佈局用 HTML 代碼實現出來大概如下所示:

<article>
class="lazy" data-original="http://www.gravatar.com/avatar"
alt="Name"
/>


@handle
Name
3h ago


Some insightful message.



  • <button>Reply/<button>

  • <button>Retweet/<button>

  • <button>Like/<button>

  • <button>.../<button>

/<article>

展示出的效果是這樣的(可以點擊這裡調試代碼):

從原型圖到成品:步步深入CSS佈局

這離我們想要的效果還遠呢。但是!所有所需的內容都齊全了。有些元素還以從左到右的順序排列。

我們可以認為,即使不用進一步設置樣式,目前的佈局效果也能達到網頁想表達的要點,這也是一個優秀的 HTML 應該達到檢查標準。

關於語義化 HTML 的說明

你可能會好奇,為何我選的是那些元素 —— article、p 等等。為何不都用 div 呢?

為何要這樣寫:

<article>






...




  • <button> ... /<button>

/<article>

而不這樣寫?








...


<button> ... /<button>

其實,每個 HTML 元素的名稱都有其特定含義,在不同場景中恰如其分地使用語義上與它們所表示的內容匹配的元素,是很好的語義化實踐。

這種寫法,首先,有助於開發者理解代碼;其次,對使用屏幕閱讀器等輔助設備的用戶比較友好。同時這樣用標籤也有利於 SEO —— 搜索引擎會試著理解這個頁面的含義,以便於顯示相關廣告來盈利、幫助搜索者找到滿意結果。

article 標籤代表文章類內容,而你可以認為推文這種東西有點類似於一篇文章。

p 標籤代表段落,而推文的內容文本有點類似於一個段落。

ul 標籤代表無序列表(與有序列表或數字序號列表相對應),在本示例中,你可以用它來存放列表信息。

我們無法用隻言片語就說清楚 HTML 元素的語義,以及何種情況用何種標籤。但大多數情況下,一個語義化元素即使其語義再不貼切,也比用 div 強,div 標籤只代表 “一塊區域”。

元素的默認樣式

是什麼決定了元素的樣式?為什麼有的元素獨佔一行,而有的元素能共處一行?

從原型圖到成品:步步深入CSS佈局

這要歸因於元素的默認樣式,這其中就有我們要探討的第一個 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 來解決。

Flexbox 的原理

CSS 的 Flex 佈局能夠把元素以行或者列的形式排布。這是一種單向的佈局系統。為了實現交叉的行和列(正如推文組件的設計那樣),我們需要添加一些容器元素來扭轉方向。

從原型圖到成品:步步深入CSS佈局

你可以在容器上設置 display: flex; 來啟用 Flex 佈局。容器本身是塊級元素(得以獨佔一行),其內部元素會成為 “Flex 子項” —— 即它們不再是行內或塊級元素了;它們都受 Flex 容器控制。

在本例中,我們會設置一些嵌套的 Flex 容器,讓該成行的成行,該成列的成列。

我們把外層容器(綠色方框)設置為列,藍色方框設置為行,而紅色方框中的元素排布在列中。

從原型圖到成品:步步深入CSS佈局

為何選 Flexbox 佈局,不選 Grid 佈局?

由於一些原因,我決定用 Flexbox 佈局而不用 Grid 佈局。我覺得 Flexbox 佈局更易於學習,也更適用於輕量級的佈局。當佈局中主要是行或者主要是列時,Flexbox 佈局的表現更出色。

另一個重點就是,即使 Grid 佈局比 Flexbox 佈局年輕,前者也撼動不了後者的地位。它們各自適用於不同的場景,對於二者,我們都要學習,技不壓身。有些情況你甚至會同時使用二者 —— 例如 Grid 佈局排布整體頁面,而 Flexbox 佈局調控頁面中的一個表單。

沒錯沒錯,在 Web 開發的世界,普遍的更替法則是後浪推前浪,但 CSS 並不如此。Flexbox 和 Grid 能夠和諧共存。

用 CSS 解決問題,條條大路通羅馬!

第四步:應用 Flexbox

好了,既然我們已經打定主意,那就開動吧。我把左側元素包進一個 div,並給元素們設置類名,便於應用 CSS 選擇器。

<article>
class="avatar"
class="lazy" data-original="http://www.gravatar.com/avatar"
alt="Name"
/>




@handle
Name
3h ago


Some insightful message.



  • <button>Reply/<button>

  • <button>Retweet/<button>

  • <button>Like/<button>

  • <button>.../<button>


/<article>

看著好像沒有變化。

從原型圖到成品:步步深入CSS佈局

這是因為 div 作為塊級元素(如果沒有空行就引入一個)是看不見的。當你需要一個包裹其他元素的容器,除了 div 之外沒有更貼合語義的選擇了。

下面咱們的第一段 CSS 代碼,我們會把它放在 HTML 文檔中 head 標籤的 style 裡:

.tweet {
display: flex;}

幹得漂亮!我們用類選擇器鎖定了所有類名為 tweet 的元素。當然目前只有一個這樣的元素,但如果有十個,那它們將都會是 Flex 容器了。

CSS 中以 . 開頭的選擇器代表類選擇器。為什麼是 .?我可不知道。你只要記住這條規則就行了。

從原型圖到成品:步步深入CSS佈局

現在文字內容都到頭像右側去了。問題是頭像圖片都扭曲變形了。

因為 Flex 容器會默認:


  • 把子項排成一行;

  • 讓子項與其內容等寬,並 ——

  • 把所有子項的高度拉平為最高子項的高度。


我們可以用 align-items 屬性來控制垂直方向的對齊方式。

.tweet {
display: flex;
align-items: flex-start;}

align-items 的默認值是 stretch,而將其設為 flex-start 後,會讓子項沿著容器頂部對齊,並且讓子項保持各自的高度。

方向的辯證:行還是列?

另外,Flex 容器的默認排列方向是 flex-direction: row;。是的,這個方向是 “行”,即使我們可能感覺那更像是兩列。要把它想成是子項們排成一行,這樣理解就舒服多了。

有點像這張花瓶的圖片,或者說兩張臉的圖片。橫看成嶺側成峰。

從原型圖到成品:步步深入CSS佈局

給文字內容更多的空間

Flex 佈局的子項僅取其所需寬度,但我們需要 content 區域儘量寬敞一些。

因此,我們要給 content 這個 div 設置 flex: 1; 屬性。(該 div 有類名,那我們就又可以用類選擇器啦!)

.content {
flex: 1;}

我們也要給頭像設置 margin,好在頭像和文字之間加點空隙:

.avatar {
margin-right: 10px;}

從原型圖到成品:步步深入CSS佈局

看起來順眼一些了吧!

margin 和 padding

那…… 為什麼用 margin 而不用 padding?為什麼要設置在頭像右側,而不是文字內容左側呢?

這是一條約定俗成的規則:在元素右側和下方設置 margin,不去碰左側和上方的 margin。

至少是在英文界面的佈局中,文檔流的方向是從左到右、從上到下的,因此,每個元素都 “依賴” 其左側和上方的元素。

在 CSS 中,每個元素的定位都受到其左側和上方的元素的影響。(至少在你遇見 position: absolute 那幫傢伙之前是這樣的。)

SoC 原則(Separation of Concerns)

從技術實現的角度來說,怎樣設置 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;}

從原型圖到成品:步步深入CSS佈局

.actions 又是一個類選擇器。原汁原味。

而 .actions li 選擇器,意即 “actions 類元素中所有的 li 元素”。它是類選擇器和元素選擇器的結合。

複合選擇器中用以分隔的空格代表著選擇範圍的縮小。事實上,CSS 是以倒序讀取選擇器的。其過程是 “先找到頁面中所有的 li,然後在這些 li 中找到類名是 actions 的那些”。但無論你用正序還是倒序的方式去理解,結果都是一樣的。(在 StackOverflow 查看更多詳解)

橫排按鈕

要橫排按鈕有好幾種方式。

一種就是設置 Flex 子項的對齊方式。你應該對設置對齊方式很熟悉,每個富文本編輯器頂部都有這種功能的按鈕:

從原型圖到成品:步步深入CSS佈局

它們把文本進行左對齊、居中對齊、右對齊以及 “兩端對齊”,也就是鋪滿整行。

在 Flexbox 佈局中,你可以用 justify-content 屬性來實現對齊。設置了 flex-direction: row(默認值,也是本文中一直在用的設置)後,可以通過 justify-content 把子項進行或左或右地對齊。justify-content 的默認值為 flex-start(因此所有元素都向左看齊)。如果我們給 .actions 元素設置 justify-content: space-between,它們就會均勻地鋪滿整行,就像這樣:

從原型圖到成品:步步深入CSS佈局

可我們想要的不是這樣的效果。如果這幾個按鈕可以不佔滿整行會更好。所以得換一種方式。

這次,我們給每個列表項設置一個右側的 margin,把它們分隔開來。還要給整個推文組件設置一個邊框,以便我們能夠直觀地衡量效果。用 1px solid #ccc 設置一個 1 像素寬的灰色實線邊框。

.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;}.actions li {
list-style: none;
margin-right: 30px;}

現在效果如下:

從原型圖到成品:步步深入CSS佈局

按鈕的排列看起來優雅多了,但灰色邊框告訴我們,所有元素都過於靠左了。還是用 padding 分配點空間吧。

.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
padding: 10px;}

現在推文組件有內邊距了,但有些地方還是很空。如果我們用瀏覽器調試工具將元素高亮顯示,就會發現 p 和 ul 元素有默認的上下 margin(在 Chrome 的調試工具中,margin 以橙色顯示,而 padding 以綠色顯示):

從原型圖到成品:步步深入CSS佈局

還有一處有意思的細節;行與行之間的上下 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 和文字的高度一樣,不論文字高度是多少”。
現在的效果如下:

從原型圖到成品:步步深入CSS佈局

現在讓我們把圖片縮小一些,並將其設置為圓形。我們將其寬高設置為 48 像素,正和 Twitter 的頭像寬高一樣。

.avatar {
margin-right: 10px;
width: 48px;
border-radius: 50%;}

我們用 border-radius 屬性來設置圓角,有好幾種方式來定義該屬性的值。如果你想要小圓角效果,可以用帶 px、em 或其他單位名稱的數字賦值。例如 border-radius: 5px 的效果:

從原型圖到成品:步步深入CSS佈局

如果將 border-radius 設為寬和高的一半(在本例中即為 24 像素),其效果就是一個圓形。但更方便的寫法是 border-radius:50%,這樣我們就不必知道具體尺寸,CSS 會計算出確切結果。甚至,如果以後寬高值變了,也無需重新修改屬性值了!

從原型圖到成品:步步深入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,新列表中的每個按鈕裡有圖標和隱藏文字:



  • <button>
    class="fas fa-reply"
    aria-hidden="true"
    >

    Reply
    /<button>


  • <button>
    class="fas fa-retweet"
    aria-hidden="true"
    >

    Retweet
    /<button>


  • <button>
    class="fas fa-heart"
    aria-hidden="true"
    >

    Like
    /<button>


  • <button>
    ...
    More Actions
    /<button>

Font Awesome 是一款圖標字體,它配合斜體標籤 i 可以展示圖標。正因為它是字體,那些可以用於文字的 CSS 屬性(例如 color 和 font-size)都適用於圖標字體。

我們在這兒做了些微調,來提升按鈕的可訪問性:


  • 特性 aria-hidden=”true” 使屏幕閱讀器忽略此圖標。

  • sr-only 類是 Font Awesome 內置的類。它讓元素在你眼前隱身,但屏幕閱讀器能讀取到它。


現在我們將要給按鈕添加一些樣式 —— 移除邊框、上色以及加大字號。還要設置 cursor: pointer,把鼠標光標變成 “手” 型,就像超鏈接的效果那樣。最後,用 .actions button:hover 選擇處於 hover 狀態的按鈕,把它們變成藍色。

.actions button {
border: none;
color: #657786;
font-size: 16px;
cursor: pointer;}.actions button:hover {
color: #1da1f2;}

下面就是推文組件光芒四射的最終效果:

從原型圖到成品:步步深入CSS佈局

如何精進 CSS 水平

最能提高 CSS 水平的就是實踐。

仿寫你喜歡的網站。設計者和藝術家稱其為 “臨摹”。我寫過一篇用臨摹的方法學 React,其中的原則也適用於 CSS。

選一些有意思的、你覺得難度大的樣式效果。用 HTML 和 CSS 臨摹該效果。如果卡殼了,用瀏覽器的調試工具看看原網站的效果是如何實現的。“栽秧苗、腿跟上、抬頭看看直不直。” :)

從原型圖到成品:步步深入CSS佈局

歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

微服務的生離死別,看這篇就對了!

Java中的對象都是在堆上分配的嗎?

在Spring Boot項目中整合使用Activiti

關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?


分享到:


相關文章: