目錄
- 前言
- CSS 基礎
- 1. 層疊
- 樣式表來源
- 理解優先級
- 源碼順序
- 經驗法則
- 繼承
- inherit 關鍵字
- initial 關鍵字
- 2. 相對單位
- em 和 rem
- 響應式面板
- 視口的相對單位
- 使用vw定義字號
- 使用calc()定義字號
- 自定義屬性(即CSS變量)
- 3. 盒模型
- 調整盒模型
前言
只需一分鐘就能學會,卻要用一輩子的時間去精通。
CSS 規則不難,容易上手,它是一種 Web 語言,與其它編程語言不同,它更像是繪畫,你可以使用 CSS 繪制頁面,而不用擔心出任何錯誤或編譯失敗的提示。但是要精通 CSS,難在需要知道在何時做何事。
CSS 基礎
CSS 其本質上就是聲明一些規則,在各種條件下產生特定的效果。最基本內容包括:層疊、相對單位、盒模型等。
1. 層疊
CSS(cascading style sheet)里的 cascade 表示層疊。
如果對于同一元素應用了多個規則時,可能包含了沖突的聲明,那么哪一個會生效呢?瀏覽器為解決這個問題會遵循一些規則來解決沖突,下面的示例中,規則規定了第二個聲明(ID 選擇器)生效,顯示綠色:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>h1 {color: red;}#page-title {color: green;}.title {color: blue;}</style>
</head>
<body><header class="page-header"><h1 id="page-title" class="title">水果</h1><nav><ul id="main-nav" class="nav"><li><a href="/">首頁</a></li><li><a href="/apple">蘋果</a></li><li><a href="/banana">香蕉</a></li><li><a href="/orange" class="featured">橘子</a></li></ul></nav></header>
</body>
</html>
當聲明沖突時,層疊會依據三種條件解決沖突:
- 樣式表的來源:樣式從哪來,包括自己寫的樣式和瀏覽器默認樣式等
- 選擇器的優先級:選擇器根據重要性有優先級
- 源碼順序:樣式的聲明順序
樣式表來源
你自己寫的樣式表屬于作者樣式表,除此之外還有用戶代理樣式表,即瀏覽器默認樣式。用戶代理樣式表優先級低,你的樣式會覆蓋它們。
用戶代理樣式在不同瀏覽器上稍有差異,但是大體上是相同的。
著重說明下 !important 聲明,這是個例外,標記為重要的聲明。此標記會被當作更高優先級的來源,優先級如下:
- 作者的 !important
- 作者的
- 用戶代理
理解優先級
如果來源無法解決沖突聲明,瀏覽器會通過檢查優先級。瀏覽器的優先級分為兩部分:HTML 的行內樣式和選擇器的樣式。
- 行內樣式
行內樣式屬于“帶作用域的”聲明,它會覆蓋任何來自樣式表或者<style>標簽的樣式。行內樣式沒有選擇器,它們直接作用于所在的元素。
為了在樣式表里覆蓋行內聲明,需要為聲明添加 !important,這樣能將它提升到一個更高優先級的來源。
- 選擇器優先級
優先級的準確規則:
- 如果選擇器的ID數量更多,則它會勝出(即它更明確)
- 如果ID數量一致,那么擁有最多類的選擇器勝出
- 如果以上兩次比較都一致,那么擁有最多標簽名的選擇器勝出。
怎么理解上面的規則,看示例,下面的優先級由低到高排列:
html body header h1 {color: blue;
}body header.page-header h1 {color: orange;
}.page-header .title {color: green;
}#page-title {color: red;
}
偽類選擇器(如:hover)和屬性選擇器(如 [type=“input”]?)與一個類選擇器的優先級相同。通用選擇器(*)和組合器(>、+、~)對優先級沒有影響
- 優先級標記
一個常用的表示優先級的方式:數值形式,通常用逗號隔開。如,“1,2,2” 表示選擇器由1個 ID、2個類、2個標簽組成。優先級最高的 ID 列為第一位,然后是類、標簽。 例如,“1,0,0” 的優先級高于 “0,2,2” 甚至 “0,10,0”?(不推薦)?,因為第一個數(ID)有最高優先級。
還有一種用4個數的標記,將最重要的位置用0或1來表示,代表是否是用行內樣式添加的。此時,行內樣式的優先級為“1,0,0,0”?。它會覆蓋通過選擇器添加的樣式,比如優先級為“0,1,2,0”?(1個ID和2個類)的選擇器。
- 優先級的思考
優先級容易發展為一種“軍備競賽”?。在大型項目中這一點尤為突出。實踐中,通常最好讓優先級盡可能低,這樣當需要覆蓋一些樣式時,才能有選擇空間。
根據上面 html 內容,給出如下示例,我們要讓 .featured 選擇器生效:
/* 優先級 1,0,1 */
#main-nav a {background-color: blue;
}/* 方法1:添加 !important。這種方法簡單也最快,但也很低級。雖然解決了當前問題,有可能會在以后帶來更多問題。當給一些聲明加上 !important時,就會先比較來源,再使用常規的優先級規則。最終會讓一切回到起點:一旦引入一個 !important,就會帶來更多的 !important */
.featured {background-color: orange !important;
}/* 方法2:將優先級提升到 1,1,0 */
#main-nav .featured {background-color: orange;
}/* 方法3:不提升第二個選擇器的優先級,降低第一個 */
/*降低優先級為 0,1,1*/
.nav a {background-color: blue;
}
/*降低優先級為 0,2,0*/
.nav .featured {background-color: orange;
}
源碼順序
層疊的最后一個判斷,是源碼順序。如果兩個聲明的來源和優先級相同,其中一個聲明在樣式表中出現較晚,或者位于頁面較晚引入的樣式表中,則該聲明勝出。
經驗法則
- 在選擇器中不要使用 ID。就算只用一個 ID,也會大幅提升優先級。當需要覆蓋這個選擇器時,通常找不到另一個有意義的ID,于是就會復制原來的選擇器,然后加上另一個類,讓它區別于想要覆蓋的選擇器。
- 不要使用 !important。它比 ID 更難覆蓋,一旦用了它,想要覆蓋原先的聲明,就需要再加上一個 !important,而且依然要處理優先級的問題。
這兩條規則是很好的建議,但不必固守它們,因為也有例外。不要為了贏得優先級競賽而習慣性地使用這兩個方法。
建議盡量不要在 JavaScript 里使用行內樣式。如果這樣做了,就是在強迫使用該包的開發人員要么全盤接受包里的樣式,要么給每個想修改的屬性加上 !important。
正確的做法是在包里包含一個樣式表。如果組件需要頻繁修改樣式,通常最好用 JavaScript 給元素添加或者移除類。這樣用戶就可以在使用這份樣式表的同時,在不引入優先級競賽的前提下,按照自己的喜好選擇編輯其中的樣式。
繼承
如果一個元素的某個屬性沒有層疊值,則可能會繼承某個祖先元素的值。繼承是順著 DOM 樹向下傳遞的。
并不是所有的屬性都能被繼承。默認情況下,只有特定的一些屬性能被繼承。比如更文本相關的屬性:color、font、line-height、white-space等,列表屬性如:list-style、list-style-position、list-style-image等
inherit 關鍵字
有時,我們想用繼承代替一個層疊值。這時候可以用 inherit 關鍵字。可以用它來覆蓋另一個值,這樣該元素就會繼承其父元素的值。
繼承有個好處,就是如果父元素樣式改變,它的樣式跟著一起改變。
initial 關鍵字
撤銷作用于某個元素的樣式,用 initial 關鍵字來實現。每一個CSS屬性都有初始(默認)值。如果將 initial 值賦給某個屬性,那么就會有效地將其重置為默認值,這種操作相當于硬復位了該值。
這么做的好處是簡單高效。如果想刪除一個元素的邊框,設置 border: initial 即可。如果想讓一個元素恢復到默認寬度,設置 width: initial 即可。
2. 相對單位
CSS 的像素單位(px)是絕對單位,即 10px 放在哪都一樣大。其他單位,如 em 和 rem 是相對單位。相對單位的值會根據外部因素發生變化。如 2em 的具體值會根據它作用到的元素而變化。
相對單位有其獨特的價值,只要我們掌握了控制其變化的方式,使用恰當,會讓代碼更簡潔靈活,也更簡單。
em 和 rem
em 是最常見的相對長度單位,適合基于特定的字號進行排版。1em等于當前元素的字號,其準確值取決于作用的元素。
.padded {font-size: 16px;padding: 1em
}
規則集指定了字號為16px,設置內邊距的值為1em。瀏覽器將其乘以字號,最終渲染為16px。重要:瀏覽器會根據相對單位的值計算出絕對值,稱作計算值(computed value)?。
當設置padding、height、width、border-radius等屬性時,使用em會很方便。這是因為當元素繼承了不同的字號,或者用戶改變了字體設置時,這些屬性會跟著元素均勻地縮放。
談到 font-size 屬性時,em 表現得不太一樣。之前提到過,當前元素的字號決定了 em。如果聲明 font-size:1.2em,這個 font-size 是根據繼承的字號來計算的。
em用在內邊距、外邊距以及元素大小上很好,但是用在字號上就會很復雜。但是,我們有更好的選擇:rem。
在文檔中,根節點是所有其他元素的祖先節點。根節點有一個偽類選擇器(:root)?,可以用來選中它自己。這等價于類型選擇器 html,但是html 的優先級相當于一個類名,而不是一個標簽。
rem 是 root em 的縮寫。rem 不是相對于當前元素,而是相對于根元素的單位。不管在文檔的什么位置使用rem,1.2rem都會有相同的計算值:1.2乘以根元素的字號。
:root {font-size: 1em;
}ul {font-size: .8rem;
}
示例中,如果根元素的字號為瀏覽器默認的字號16px(根元素上的em是相對于瀏覽器默認值的)?。無序列表的字號設置為0.8rem,計算值為12.8px。因為相對根元素,所以所有字號始終一致,就算是嵌套列表也一樣。
與 em 相比,rem 降低了復雜性。實際上,rem 結合了 px 和 em 的優點,既保留了相對單位的優勢,又簡單易用。
但是,使用還得根據場景來定。拿不準的時候,用rem設置字號,用px設置邊框,用em設置其他大部分屬性。
響應式面板
我們可以根據屏幕尺寸,用媒體查詢改變根元素的字號。這樣就能夠基于不同用戶的屏幕尺寸,渲染出不同大小的面板。
媒體查詢,即 @media 規則,可以指定某種屏幕尺寸或者媒體類型(比如,打印機或者屏幕)下的樣式。這是響應式設計的關鍵部分。
:root {font-size: 0.75em;
}@media (min-width: 800px) {:root {font-size: 0.875em;}
}@media (min-width: 1200px) {:root {font-size: 1em;}
}
通過給頁面根元素設置不同字號,我們響應式地重新定義了整個網頁的 em和 rem。也就是說,即使不直接修改面板的樣式,它也是響應式的。縮放瀏覽器窗口可以看到這些變化。
視口的相對單位
視口:瀏覽器窗口里網頁可見部分的邊框區域。它不包括瀏覽器的地址欄、工具欄、狀態欄。
- vh:視口高度的1/100
- vw:視口寬度的1/100
- vmin:視口寬、高中較小的一方的1/100(IE9中叫vm,而不是vmin)
- vmax:視口寬、高中較大的一方的1/100(本書寫作時IE和Edge均不支持vmax)[插圖]。
如,50vw 等于視口寬度的一半,25vh等于視口高度的25%。vmin 取決于寬和高中較小的一方,這可以保證元素在屏幕方向變化時適應屏幕。在橫屏時,vmin 取決于高度;在豎屏時,則取決于寬度。
視口相對長度非常適合展示一個填滿屏幕的大圖。我們可以將圖片放在一個很長的容器里,然后設置圖片的高度為100vh,讓它等于視口的高度。
使用vw定義字號
相對視口單位有一個不起眼的用途,就是設置字號,它或許比用 vh 和 vw 設置元素的寬和高還要實用。
比如,我們設置 font-size: 2vw。 即,在一個1200px的桌面顯示器上,計算值為24px(1200的2%)?。在一個768px寬的平板上,計算值約為15px(768的2%)?。這樣做的好處在于元素能夠在這兩種大小之間平滑地過渡,這意味著不會在某個斷點突然改變。當視口大小改變時,元素會逐漸過渡。
但有一個問題,24px 在大屏上來說有可能太大了。更糟糕的是,在手機上可能會縮小到只有7.5px。如果能夠保留這種縮放的能力,但是讓極端情況緩和一些就更棒了。CSS 的 calc() 函數可以提供幫助。
使用calc()定義字號
calc() 函數內可以對兩個及其以上的值進行基本運算。當要結合不同單位的值時,calc() 特別實用。支持的運算包括:加(+)?、減(?)?、乘(×)?、除(÷)?。加號和減號兩邊必須有空白。
示例:calc() 結合 em 和 vw 兩種單位。刪除之前樣式表的基礎字號(以及相關的媒體查詢)?,換成如下代碼:
:root {font-size: calc(0.5em + 1vw);
}
打開網頁,慢慢縮放瀏覽器,字體會平滑地縮放。0.5em 保證了最小字號,1vw 則確保了字體會隨著視口縮放。這段代碼保證基礎字號從手機里的11.75px一直過渡到1200px的瀏覽器窗口里的20px。可以按照自己的喜好調整這個值。
自定義屬性(即CSS變量)
要定義一個自定義屬性,只需要像其他CSS屬性那樣聲明即可。看個例子:
:root {--main-font: Helvetica, Arial, sans-serif;
}p {font-family: var(--main-font);
}
這個代碼清單定義了一個名叫 --main-font 的變量。將其值設置為一些常見的 sans-serif 字體。變量名前面必須有兩個連字符(–)?,用來跟CSS 屬性區分,剩下的部分可以隨意命名。變量必須在一個聲明塊內聲明。這里使用了:root選擇器,因此該變量可以在整個網頁使用。
變量聲明本身什么也沒做,我們使用時才能看到效果。自定義屬性就像作用域變量一樣,因為它的值會被后代元素繼承。
首先還是在 :root 選擇器的規則集中定義變量。這很重要,如此一來這些值就可以提供給根元素(整個網頁)下的任何元素。當根元素的后代元素使用這個變量時,就會解析這里的值。
3. 盒模型
頁面中的每一個標簽,都可以看做是一個 “盒子”,通過盒子的視角,我們可以更方便的進行布局。
盒子模型:CSS 中規定每個盒子分別由:內容(content)、內邊距(padding)、邊框(border)、外邊距(margin)構成。
計算公式:
- 盒子寬度 = 左邊框 + 左 padding + 內容寬度 + 右 padding + 右邊框
- 盒子高度 = 上邊框 + 上 padding + 內容寬度 + 下 padding + 下邊框
調整盒模型
CSS 中我們可以使用 box-sizing 屬性來調整盒模型的行為。
box-sizing 的默認值為 content-box,這意味任何指定的寬或高都只會設置內容盒子的大小。
將 box-sizing 設置為 border-box 后,height 和width 屬性會設置內容、內邊距以及邊框的大小總和。
示例,設置全局的 border-box:
*,
::before,
::after {box-sizing: border-box;
}
用通用選擇器(*)選中了頁面上所有元素,并用兩個選擇器選中了網頁的所有偽元素。將這段代碼放到樣式表開頭已是普遍做法了。
但是,如果網頁中使用了帶樣式的第三方組件而且沒有考慮到使用者會修改盒模型時,就可能會因此破壞其中一些組件的布局。因為全局設置 border-box 時使用的通用選擇器會選中第三方組件內的每個元素,修改盒模型可能會有問題,所以最終需要寫另外的樣式將組件內的元素恢復為 content-box。
下面示例,讓全局修改盒模型為border-box更穩健:
:root {box-sizing: border-box;
}*,
::before,
::after {box-sizing: inherit;
}
盒模型通常不會被繼承,但是使用 inherit 關鍵字可以強制繼承。
開發過程中,建議將上面代碼加到 CSS 中,因為從長遠來看,這會省去很多麻煩。
但是,如果給已有的樣式表加上上面代碼后可能有問題,尤其是當你已基于默認的內容盒模型寫了很多樣式后。如果非要給已有項目加上這段代碼,那么一定要徹底檢查一遍看會不會有問題。