學過一天前端的小白都知道,html 裡面有一個標籤叫做 ol——order list。通過 ol 和 li 的巢狀,我們能夠得到前面有數字標號的列表。這位小白如果再多學幾天,也許還會知道,css 裡有一個屬性叫做 list-style-type。通過它,能夠控制列表標號的型別,從大小圓點、到數字、大小寫英文、羅馬字母不等。
不過,這位小白也許再學很久都不會接觸到 css counter。這是一個古老但實用的屬性,給予我們更靈活更強大地控制列表標號的能力。下面我們就來介紹它。
css counters 的屬性
- counter-reset
- counter-increment
- counter() / counters()
依次說明:
1.counter-reset。
明譯為計數器重置。形如:counter-reset: level1
其中,level1 只是示例,實際上是可以任意命名的一個名字識別符號。
按我的理解,counter-reset 的真實意思是:在目標元素所在的層級中定義一個計數器。
2.counter-increment
明譯為計數器累加。形如: counter-increment: level1 1
其中,level1 是通過 counter-reset 定義的計數器名,這裡的 1 也可以是任意其他整數,甚至可以是負數。當瀏覽器渲染頁面時,帶這個屬性的元素每出現一次,當前層級內對應名字的計數器就增加相應的值。如不寫,預設是 1。
3.counter() / counters()
前面兩個屬性定義了計數器和計算規則,不過,這些計數器的計算目前都是在記憶體中進行的。counter() 和 counters() 就負責把計數器顯示出來。這兩個計算方法要和偽元素的 content 屬性搭配食用。形如:content: counter(level1)
。counter 計算符前後可以隨意加字串來對最後的效果做拼接。
*注意:當 couner() 一個沒有定義過的計數器時,會顯示 1。
下面來看一個最簡單的例子。
例1:
html:
<div class="level1">
<div class="level1-item">foo</div>
<div class="level1-item">bar</div>
</div>複製程式碼
css:
.level1 {
counter-reset: level1;
}
.level1-item {
counter-increment: level1 1; /* counter-increment寫到偽類的選擇器中也是可以的 */
}
.level1-item:before {
content: 'step' counter(level1) '.';
}複製程式碼
效果:
很簡單是吧。至此,想必讀者對 css counter 的屬性的基本用法已經有所瞭解了。下面開始進階了,請繫好安全帶。
進階應用
使用 list-style-type
counter() 和 counters() 方法內還可以應用 list-style-type。只要把 list-style-type 屬性的合法值寫在計數器名後即可,中間用逗號分隔。
例2 (其餘 css 和 html 保持一致):
.level1-item:before {
content: counter(level1, upper-roman) '.';
}複製程式碼
效果:
重新開始計數
在同一層級中,可以通過重複宣告相同名字的計數器來打斷(或者說是覆蓋)之前的計數,重新開始。
例3:
<div class="level1">
<div class="level1-item">foo</div>
<div class="level1-item">bar</div>
<div class="level1-item break">baz</div>
<div class="level1-item">qux</div>
</div>複製程式碼
.level1 {
counter-reset: level1;
}
.break {
counter-reset: level1;
}
.level1-item:before {
content: counter(level1, upper-roman) '.';
counter-increment: level1 1;
}複製程式碼
效果:
多層巢狀計數
重點來了。到上面為止,所有的效果我們都可以用普通的 ol 標籤來實現,但下面的多層巢狀計數則是 css counter 獨有的絕技,光用 ol 是實現不了的。(你要手動硬寫標號那當我什麼都沒說)而這也是 css counter 最主要的用武之地。
例4:
<div class="level1">
<div class="level1-item">
china
<div class="level1">
<div class="level1-item">newbee</div>
<div class="level1-item">
lgd
<div class="level1">
<div class="level1-item">lgd</div>
<div class="level1-item">lfy</div>
</div>
</div>
</div>
</div>
<div class="level1-item">
world
<div class="level1">
<div class="level1-item">liquid</div>
</div>
</div>
</div>複製程式碼
.level1 {
counter-reset: level1;
}
.level1 div {
padding-left: 10px;
}
.break {
counter-reset: level1;
}
.level1-item:before {
counter-increment: level1;
content: counters(level1, '-') '. '
}複製程式碼
效果:
在上面的例子裡,我們首次使用了 counters() 函式,它的第一個引數是計數器名字,第二個引數是一個連線符。counters() 函式會在文件中遍歷查詢指定計數器,並按照巢狀關係,用連線符把不同層級上的同名計數器的值連線起來,形成標號。
成是成了,不過我認為上面這個方法並不是一個好的實踐,原因有二:
第一,列表中明明出現了三個層級,但我們編碼時卻一直在使用 level1 這一個計數器,讓人困惑。
第二,我們引入了無意義的 html 標籤 <div class="level1"></div>
。比如,newbee,lgd 都是中國戰隊,我們希望直接放到 china 的後面,而不是用一個額外的不具備什麼語義的 level1 標籤來把他們包裹起來。讀者也可以發現,目前的 html 結構還是比較複雜的。
使用多個計數器的多層巢狀計數
仔細思考一下,我們發現,上面的問題都是因為我們只使用了一個計數器。考慮到 content 屬性裡可以使用多個 counter() 函式,我們完全可以把巢狀層級拆成多個計數器。
改造上面的例子。
例5:
<div class="level1">
<div class="level1-item">
china
<div class="level2-item">newbee</div>
<div class="level2-item">
lgd
<div class="level3-item">lgd</div>
<div class="level3-item">lfy</div>
</div>
</div>
<div class="level1-item">
world
<div class="level2-item">liquid</div>
</div>
</div>複製程式碼
.level1 {
counter-reset: level1;
}
.level1-item:before {
counter-reset: level2;
counter-increment: level1;
content: counter(level1) '.';
}
.level2-item:before {
counter-reset: level3;
counter-increment: level2;
content: counter(level1) '-' counter(level2) '.';
}
.level3-item:before {
counter-increment: level3;
content: counter(level1) '-' counter(level2) '-' counter(level3)'.';
}複製程式碼
效果:
完美,和之前的一模一樣。使用這種實現方式,html 的層級減少了,而通過引入了 level2 和 level3,整體的結構變得更加清晰了。
可是有的同學又要說了,不行,我還是更喜歡之前那種單個計數器的方式,一個 counters() 全搞定,酷炫。而且雖然 html 比現在多,但是 css 少呀!
嗯,說的有點道理,不過當遇到下面這種需求時,恐怕你只有使用多個計數器了。
結合巢狀計數器與自定義 list-style-tyle
上面的巢狀列表有一個特點,即每個層級的標號使用的都是數字。但實際需求可能更加靈活,比如第一級用一個大寫字母,第二級用第一級標號+數字,第三級用小寫羅馬數字等。這種情況下,用一個 counters() 是實現不了的。
例6:
.level1 {
counter-reset: level1;
}
.level1-item:before {
counter-reset: level2;
counter-increment: level1;
content: counter(level1, upper-alpha) '.';
}
.level2-item:before {
counter-reset: level3;
counter-increment: level2;
content: counter(level1, upper-alpha) '-' counter(level2) '.';
}
.level3-item:before {
counter-increment: level3;
content: counter(level3, lower-roman)'.';
}複製程式碼
效果:
完美實現!
從 counters() 的標號錯亂來深入理解 css counters
回過頭看例4,如果我們把 html 的結構改成這樣:
<div class="level1">
<div class="level1-item">國內戰隊</div>
<div class="level1">
<div class="level1-item">newbee</div>
<div class="level1-item">lgd</div>
<div class="level1">
<div class="level1-item">lgd</div>
<div class="level1-item">lfy</div>
</div>
<div class="level1-item">vg</div>
</div>
<div class="level1-item">國外戰隊</div>
<div class="level1">
<div class="level1-item">liquid</div>
</div>
</div>
</div>複製程式碼
css 保持不變,結果就變成了:
好吧,我們看到,原本標號應該是 1-3 的項變成了1-2-3,原本應該是 2 的項變成了 1-3,這是怎麼回事兒呢?
分析當前結構和正確結構的區別,我們發現,最大的區別在於當前結構把概念上屬於子級的 level1 容器寫成了列表上一級的兄弟元素。還別說,如果不注意,我也很容易就寫成這樣了。
但為啥這麼寫就不對了呢?
按我的理解,計數器擁有一個活動範圍,這個範圍並不是計數器所在的元素本身,而是這個計數器所在的元素的父元素。換句話說,如果在一個元素上定義了計數器,那麼這個元素的所有兄弟元素都能訪問到這個計數器,而且將優先訪問這個計數器。
優先順序:
在元素自身上定義的計數器 > 在元素兄弟元素上定義的計數器 > 在元素父元素上定義的計數器
另外,元素無法訪問到其兄弟元素的子元素定義的計數器。
在上面的例子中,緊跟在 level-1 後的兄弟元素 level-1-item 也能訪問到 level-1 定義的計數器,而這個計數器在邏輯上已經是深層級的了。所以標號原本應該是 1-3 的項實際上讀取的是它前面那個兄弟元素 level-1 定義的計數器,這個計數器實際上是第三層級,於是在原來的基礎上順著 +1,成了 1-2-3。
到頭來,為什麼一定要用 css counters ?
有些拼命拆我臺的同學又要說了,css counters 太麻煩了,我還是不想用它。就算出現了巢狀列表的標號的需求,我直接用純文字,把類似 A-1 這樣的文字寫進標籤中不就行了?
行,如果你真的這麼幹了,我敬你是條漢子。但你有沒有想過,如果在列表中增加了一條或者刪除了一條,當前層級之後的所有列表項的標號,可都要變了。最極端的情況:如果產品說要把列表項的第一項刪掉···?
嗯,教你兩個選擇。第一,打死產品。第二,打一開始就使用 css counters。
最後,來看一下相容性
css counters 系列屬性 css2 就加了,所以相容性特別好,一片原諒色,大家放心大膽使用吧。