本文基於 CSS 2.1 規範中文版整理。
使用瀏覽器 Chrome 70 進行測試。
前言
也許有人會說,都快 2019 年了怎麼還讀 CSS2.1 規範。一方面,現在最新的 CSS (core) 規範是 CSS2.2(以下截圖來自 www.w3.org/TR/CSS/ ),又因為 CSS2.1 有中文的版本,並且和 CSS2.2 規範差異性不是很大,基於偷懶的目的最終選擇閱讀了 CSS2.1 規範。
記得面試的時候,面試官說 “你的 JavaScript 掌握得比大多數人好很多,但你的 CSS 還需要再加強”。今年六月畢業後,我正式成為一名前端工程師,閱讀 CSS 規範也因此進入自己今年的 TODO list 中。
前後花了兩個月的零散時間,在上週我終於完成了閱讀,對 CSS 的理解也稍微更系統全面了一些。在自己閱讀的過程中,有些感覺是大家平常容易忽視,但有可能用到或是本身比較有趣的小知識點,便以此為主題,在此基礎上適當擴充套件,整理了一篇文章和大家分享。
只要一杯咖啡的時間,一起來回顧一下規範裡那些可能不太能引起你注意的地方吧。
閱讀愉快,請多指教。
關鍵字、屬性和識別符號
以 - 或 _ 開頭的關鍵字和屬性名是為特定供應商擴充套件保留的。
CSS 中識別符號只能包含字元 [a-zA-Z0-9]、ISO10646 字元中 U+00A0 及之後的字元,以及 - 和 _,不能以 1 個數字、2 個連字元、或者後面跟著數字的連字元開頭。
這也意味著,選擇器可以是中文的(雖然並不推薦)。
<style>
.中文 {
color: red;
}
</style>
....
<p class="中文">這是一段中文選擇器文字。</p>
複製程式碼
此時,<p>
標籤內的文字將變為紅色。
@import
使用者代理必須忽略所有出現在塊內部,或者在除 @charset
和 @import
規則外任何無法忽略的語句後面的 @import
規則。
這也意味著,@import
規則必須先於除@charset
和其它的 @import
規則外的所有規則。
@import "subs.css"
h1 { color: blue; }
@import "list.css"
複製程式碼
上述程式碼中,第二個 @import
語句是非法的,解析器將會忽略這條規則。
另外,@import
規則可以加上媒體查詢型別,表示只在滿足某個媒體型別時引入樣式表。
@import "landscape.css" screen and (orientation:landscape)
複製程式碼
編碼和 @charset
當一個樣式表被嵌進其它文件時,比如 HTML 中的 style
元素或者 style
屬性,樣式表會共享整篇文件的字元編碼。
如果一個樣式表處於一個獨立檔案中,則按下列順序(從最高優先順序到最低)確定樣式表的字元編碼:
- HTTP 協議中
Content-type
欄位的charest
引數 - 檔案的 BOM 編碼或
@charset
<link charset="">
或者來自連結機制的其它後設資料- 要引入的樣式表或者文件的字符集
- 假定為 UTF-8
使用 @charset
規則的編寫者必須把該規則放在樣式表的開頭,前面不允許有任何字元。使用者代理必須忽略任何不在樣式表開頭的 @charset
規則。
:first-letter 偽元素
:first-letter
偽元素選擇一個塊的第一行(第一個格式化行塊)的第一個字母(或數字),如果這一行中在它前面沒有跟著任何其它內容(例如圖片或者 inline table)的話。表格單元或者inline-block
元素的首字母不能作為其祖先元素的首字母。
<style>
.description:first-letter {
color: white;
}
</style>
<p class="description">“some text”</p>
<p class="description">some text</p>
<p class="description"><img src="" />some text</p>
複製程式碼
實際效果如圖:
:before 與 :after 偽元素
:before
和 :after
偽元素會從文件樹中它們附著的元素上繼承所有可繼承的屬性。對於不可繼承的元素,將取其初始值。
用法示例:
p { color: red; display: block; }
p:before { content: 'T'; }
複製程式碼
如上例,此時,:before 偽元素將呈現紅色。因為 display
屬性不可繼承,將取其初始值 inline
。
擴充套件:偽元素的單冒號和雙冒號
CSS3 選擇器草案中區分了偽元素和偽類(CSS-Selectors Level 4),偽類仍以一個引號開頭,偽元素則以兩個引號開頭。對於 CSS1 和 CSS2 中存在的 :before
、:after
、:first-line
和 :first-letter
偽元素,使用者代理必須同時支援它們單引號和雙引號的形式,對於其它新引入的偽類,使用者代理將不支援其單引號的形式。
選擇器與層疊
選擇器中,文件語言元素名的大小寫敏感性取決於文件語言,例如在 HTML 中元素名是大小寫不敏感的,而在 XML 中元素名是大小寫敏感的。
層疊順序
樣式表可能有 3 種不同的來源:編寫者、使用者和使用者代理(如瀏覽器)。
為了找出一個元素或屬性組合的值,使用者代理必須按照下列(步驟)排序:
- 找出目標媒體型別下,所有適用於該元素和目標屬性的宣告
- 根據重要性(@important)規則和來源排序,優先順序從低到高為:
- 使用者代理宣告
- 使用者常規宣告
- 編寫者常規宣告
- 編寫者重要宣告
- 使用者重要宣告
- 相同重要性和來源的規則根據選擇器的特殊性(specificity)排序,更特殊的選擇器將重寫一般的。偽元素和偽類被分別算作常規元素和類
- 最後,根據指定順序排序:如果兩個宣告的權重,來源和特殊性都相同,後製定的生效。
@import
引入的樣式表中的宣告被認為在樣式表自身的所有宣告之前
計算選擇器的特殊性(specificity)
一個選擇器的特殊性(a-b-c-d)根據下列規則計算(a到d權重依次遞減):
- 如果宣告來自一個
style
屬性而不是一條選擇器樣式規則,算1,否則就是0(=a) - 計算選擇器中 ID 屬性的數量(=b)
- 計算選擇器中其它屬性和偽類的數量(=c)
- 計算選擇器中元素名和偽元素的數量(=d)
* {} /*a=0, b=0, c=0, d=0*/
li {} /*a=0, b=0, c=0, d=1*/
.description {} /*a=0, b=0, c=1, d=0*/
*[rel=up] {} /*a=0, b=0, c=1, d=0*/
#element {} /*a=0, b=1, c=0, d=0*/
style="" /*a=1, b=0, c=0, d=0*/
複製程式碼
也就是說,儘管 #p123
比 [id=p123]
選中的物件相同,但ID 選擇器比屬性選擇器擁有更高的特殊性。
補充
之前看過一些部落格,在提到層疊的特殊性的時候,用 1000, 100, 10, 1 的權重去描述上述的 a-b-c-d 的計算方式。實際上這並不準確。我們可以通過簡單的實驗推翻上述的說法。
如下例,我們定義了類名為 zero
的 div
,裡面巢狀了十層 div
,最內的一層指定了 id 為last
。如果按照10倍遞增的演算法,巢狀十一層的 class
的特殊性為 110,使用一個 id 的特殊性為 100,背景呈現黑色。如果按照 a-b-c-d 的演算法,使用前者的特殊性為 0-0-11-0,後者特殊性為 0-1-0-0,背景將呈現紅色。
通過觀察連用十一個 class 和一個 id 的表現,即可得出結論。
<style>
#last {
background: red;
}
.zero .one .two .three .four
.five .six .seven .eight
.nine .ten {
background: black;
height: 100px;
width: 100%;
}
</style>
<div class="zero">
<div class="one">
<div class="two">
<div class="three">
<div class="four">
<div class="five">
<div class="six">
<div class="seven">
<div class="eight">
<div class="nine">
<div class="ten" id="last"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
複製程式碼
如圖所示,背景呈現紅色,所以 1000, 100, 10, 1 的計算方式是不準確的。
不過,由於實現上的問題,可能會存在有限多個 class
優先順序比一個 id
高的情況。張鑫旭在12年分享過一篇《有趣:256個class選擇器可以幹掉1個id選擇器》。主要是因為寫這篇文章的時候有些瀏覽器使用了8位計數器對 class
的數量進行計算,當 class
達到256個時由於發生進位將會出現權重比 id
高的情況。之後的瀏覽器使用了更高位數的計數器,理論上仍可能存在邊界值,但將很難達到。
content 中的 attr 屬性
content: attr(X)
複製程式碼
attr(X)
函式返回一個字串,字串的值為該選擇器選中物件的 X 屬性的值。如果該物件沒有 X 屬性,則返回一個空字串。
用法示例:
p:after { content: ", " attr(data-name); }
複製程式碼
<p data-name="Thorn">Hello</p>
複製程式碼
此時將會渲染出 “Helo, Thorn”。
計數器
計數器使用大小寫敏感的識別符號。自動編號通過 counter-increment
和 counter-reset
屬性控制。
如果給同一個計數器指定了多次 counter-reset
或者 counter-increment
的值,計數器的每次重置或遞增會按指定的順序處理。
/* 第二個 section 省略了重置的值,會被置預設值 0, section 將先重置為 2,最終重置為 0 */
h1 { counter-reset: section 2 section; }
/* 第一個 section 省略了遞增的值,會被置預設值 1, section 先遞增 1, 再遞增 2, 相當於遞增 3*/
h1 { counter-increment: section section 2; }
複製程式碼
另外,counter-reset
屬性遵循層疊原則,如果要同時重置兩個計數器,它們必須同時指定。
h1 { counter-reset: section 0 image 0; }
/* 下面的寫法會發生層疊導致只有一個計數器生效 */
h1 { counter-reset: section 0; }
h1 { counter-reset: image 0; }
複製程式碼
巢狀計數器與作用域
計數器是自巢狀的,如果重置一個位於後代元素或偽元素的中的計數器,則會自動建立一個新的計數器例項。計數器的作用域從文件中具有 “counter-reset
+ 該計數器” 的第一個元素開始,包括該元素的後代和後續兄弟及其後代,但不包括處於同名計數器作用域中的任何元素。
<style>
ol {
counter-reset: section;
list-style-type: none;
}
li:before {
counter-increment: section;
content: "第" counters(section, ".") "章";
}
</style>
<ol>
<li>item</li> <!--第1章-->
<li> <!--第2章-->
<ol>
<li>item</li> <!--第2.1章-->
</ol>
</li>
<li>item</li> <!--第3章-->
</ol>
複製程式碼
效果如圖所示
display:none
的元素中的計數器
一個 display
值設定為 none
的元素不會讓計數器遞增或重置,無法生成的偽元素也不會讓計數器遞增或重置,然而 visibility
被設定 hidden
的元素會讓計數器遞增或重置。
列表
一個具有display: list-item
的元素會為該元素的內容生成一個主塊盒,還可能生成一個標記盒作為視覺化指示,說明該元素是一個列表項。
background
屬性只適用於主盒,外部的標記盒是透明的。(不過,如果將 list-style-position
的值設定為 inside
的話,由於標記盒的背景是透明的,主盒設定的背景將會透過來,視覺上也相當於同時給主盒和標記盒加上了背景)。
背景
根據盒模型,背景指的是內容,內邊距和邊框區的背景。邊框顏色和樣式可以通過邊框屬性來設定,而外邊距總是透明的。
<style>
.transparent-border {
border: 10px solid rgba(0, 0, 0, 0);
color: white;
background: blue;
}
</style>
<p class="transparent-border">將 border 的顏色設定為透明時,背景的顏色將透過來。</p>
複製程式碼
背景屬性是不可繼承的,但因為 background-color
的初始值為 transparent
,父級盒的背景將透過來。
另外,當同時設定 background-image
屬性和 background-color
屬性時,如果影象可用則將被渲染在背景色之上。這也意味著,在影象的透明部分,背景色是可見的。
輪廓(outline
)
outline
與 border
的區別:
outline
不佔空間,顯示或隱藏不會導致重排或者溢位outline
可以不是矩形的- 所有方向的
outline
都相同,與border
相比,不存在outline-top
或outline-left
屬性 - 如果元素被拆分成了多行,與
border
相比,outline
在行框的開始或者結束出不是斷開的,而是總會盡量完全連線起來(效果如下圖) - 因為
outline
不影響格式化,它可能會與頁面上的其他元素重疊
outline-width
與 background-width
接受相同的值(hidden 除外)。
outline-style
與 background-style
接受相同的值(hidden 除外)。
outline-color
與 background-color
接受相同的值。此外 outline-color
還可以設定為 invert
,用來對螢幕的畫素取反色(但不是所有瀏覽器都支援該屬性)。
預設樣式
在不指定字型大小的情況下,標題字號略粗於常規字型,h4
的字型大小和常規字型大小相同,h5
和 h6
字型略小於常規字型。
h1, h2, h3, h4, h5, h6 { font-weight: bolder; }
h1 { font-size: 2em; margin: .67em 0; }
h2 { font-size: 1.5 em; margin: .75em 0; }
h3 { font-size: 1.17em; margin: .83em 0; }
h4 { margin: 1.12em 0; }
h5 { font-size: .83em; margin:1.5em 0; }
h6 { font-size: .75em; margin: 1.67em 0; }
複製程式碼