CSS counters 深度介紹

@㍿社長發表於2017-10-13

學過一天前端的小白都知道,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 counters 深度介紹

很簡單是吧。至此,想必讀者對 css counter 的屬性的基本用法已經有所瞭解了。下面開始進階了,請繫好安全帶。


進階應用

使用 list-style-type

counter() 和 counters() 方法內還可以應用 list-style-type。只要把 list-style-type 屬性的合法值寫在計數器名後即可,中間用逗號分隔。

例2 (其餘 css 和 html 保持一致):

.level1-item:before {
    content: counter(level1, upper-roman) '.';
}複製程式碼

效果:

CSS counters 深度介紹

重新開始計數

在同一層級中,可以通過重複宣告相同名字的計數器來打斷(或者說是覆蓋)之前的計數,重新開始。

例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;
}複製程式碼

效果:

CSS counters 深度介紹

多層巢狀計數

重點來了。到上面為止,所有的效果我們都可以用普通的 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, '-') '. '
}複製程式碼

效果:

CSS counters 深度介紹

在上面的例子裡,我們首次使用了 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)'.';
}複製程式碼

效果:

CSS counters 深度介紹

完美,和之前的一模一樣。使用這種實現方式,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)'.';
}複製程式碼

效果:

CSS counters 深度介紹

完美實現!

從 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 保持不變,結果就變成了:

CSS counters 深度介紹

好吧,我們看到,原本標號應該是 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 深度介紹

css counters 系列屬性 css2 就加了,所以相容性特別好,一片原諒色,大家放心大膽使用吧。


相關文章