CSS counters 深度介绍

学过一天前端的小白都知道,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 就加了,所以兼容性特别好,一片原谅色,大家放心大胆使用吧。


转载于:https://juejin.im/post/59df5acf6fb9a0452404d31f

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值