聊聊CSS的::marker
CSS的::marker
是一个伪元素,最早接触时间应该是在四年前。四年前在《试探列表中的::marker
》一文中就提到过CSS的::marker
伪元素,只不过在当时,该伪元素只是大家探索性的在聊该属性,而时至今日却不同,Firefox 68已经追随Safari11.1对该伪元素支持了。也就是说,该伪元素离我们越来越近了,以后我们可以使用::marker
帮我们做更多的处理。简而言之,使用::marker
伪元素,可以对列表做一些有趣的事情,在本文中,我们将深入的聊聊该伪元素。
初识CSS的::marker
::marker
是CSS的伪元素,现在被纳入到CSS Lists Module Level 3规范中。在该规范中涵盖了列表和计算数器相关的属性,比如我们熟悉的list-style-type
、list-style-position
、list-style
、list-item
、counter-increment
、counter-reset
、counter()
和counters()
等属性。
在CSS中display
设置list-item
值之后就会生成一个Markers标记以及控制标记位置和样式的几个属性,而且还定义了计数器(计数器是一种特殊的数值对象),而且该计数器通常用于生成标记(Markers)的默认内容。
一时之间,估计大家对于Markers标记并不熟悉,但对于一个列表所涉及到的相关属性应该较为熟悉,对于一个CSS List,它可以涵盖了下图所涉及到的相关属性:
如果你对CSS List中所涉及到的属性不是很了解的话,可以暂时忽略,随着后续的知识,你会越来越清楚的。
解构一个列表
虽然我们在Web的制作中经常会用到列表,但大家可能不会过多的考虑列表相关的属性或使用。就HTML语义化出发,如果遇到无序列表的时候会使用<ul>
,遇到有序列表的时候会使用<ol>
,但在有些场景(或不追求语义化的同学)会采用其他的标签元素,比如说<div>
。针对这个场景,会采用display
设置为list-item
。如此一来会创建一个块级别的框,以及一个附加的标记框。同时也会自动增加一个隐含列表项计算数器。
ul
和ol
元素默认情况之下会带有list-style-type
、list-style-image
和list-style-position
属性,可以用来设置列表项的标记样式。同样的,带有display:list-item
的元素生成的标记框,也可以使用这几个属性来设置标记项样式。
list-style-type
的属性有很多个值:
取值不同时,列表符号(也就是Marker标识符)会有不同的效果,比如下面这个示例所示:
在CSS中给列表项设置类型的样式风格可以通过list-style-type
和list-style-image
来实现,但这两个属性设置列表项的样式风格会有所限制。比如要实现类似下图的列表项样式风格:
值得庆幸的是,CSS的::marker
给予我们更大的灵活性,可以让我们实现更多的列表样式风格,而且灵活性也更大。
创建marker标记框
HTML中的ul
和ol
元素会自动创建marker
标记框。如果通过浏览器调试器来查看的话,你会发现,不管是ul
还是ol
的子元素li
会自带display:list-item
属性设计(客户端默认属性),另外会带有一个默认的list-style-type
样式设置:
这样一来,它自身就默认创建了一个marker
标记框,同时我们可以通过::marker
伪元素来设置列表项的样式风格,比如下面这个示例:
ul ::marker,
ol ::marker {
font-size: 200%;
color: #00b7a8;
font-family: "Comic Sans MS", cursive, sans-serif;
}
你会看到效果如下所示:
特别声明:请使用Firefox 68+ 或 Safari 11.1+ 浏览器查看Demo效果。如无特别声明,接下来的示例都是在Firefox68+ 或 Safari 11.1+中的效果。
对于非列表元素,可以通过display: list-item
来创建Marker标记,这样就可以在元素上使用::marker
伪元素来设置项目符号的样式。虽然通过display:list-item
在形式上看上去像列表项,但在语义化上并没有起到任何的作用。
如果在非列表项的元素是让
display:list-item
的标签更具可读性或让读屏软件能识别出是列表项,我们可以借助ARIA相关的特性来实现,有关于这方面详细的介绍可以阅读《ARIA Lists》一文。
不过我们在这篇文章中不是来讨论可访问性的相关问题,而是讨论::marker
的特性,在接下来的内容中将会深入讨论::marker
。你会发现,在某些情况下,带有display:list-item
的元素,使用::marker
会让列表标识更具可创造性。
在深入探讨::marker
使用之前,大家要知道,元素必须要具备一个Marker标记框,对于非列表项的元素需要显式的使用display:list-item
来创建Marker标记框。
CSS的
display
属性是一个非常重要的属性,现在被纳入在CSS Display Module Level 3中。CSS的display
属性可以改变任何一个元素的框模型。而且在Level 3规范中给display
引用了两个值的语法,比如使用display: inline list-item
可以创建一个内联列表项。
如果你想了解更多有关于display
属性相关的知识,建议您花一点时间阅读下面几篇文章:
- Digging Into The Display Property: The Two Values Of Display
- Digging Into The Display Property: Box Generation
- Digging Into The Display Property: Grids All The Way Down
- CSS的
display:contents
- CSS的
display:flow-root
- More accessible markup with
display: contents
- Display: Contents Is Not a CSS Reset
- 如何理解CSS的display属性
- Display: Contents Is Not a CSS Reset
- An introduction to CSS Containment
- CSS * Display Property in Depth
::marker的基本使用
前面的小示例中,其实我们已经领略到了::marker
的魅力。在列表项li
中,其实已经带有Marker标记框,可以借助::marker
伪元素来设置列表标记的样式。
我们先来回忆一下,CSS的::marker
还未出现(或者说不支持的浏览器)时,要对列表项设置不同的样式,都是通过li
上来控制(看上去继承了li
上的样式)。虽然能设置列表样式,但还是具有一定的局限性,灵活度不够大 —— 特别是当列表项标记样式和内容要区分时。
CSS的::marker
会让我们变得容易的多。从前面的示例中我们可以了解到,::marker
伪元素和列表项内容是分开的,正因此,我们可以独立为两个部分设计不同的样式。这在以前的CSS版本中是不可能的(除非借助::before
伪元素来模拟,稍后也会介绍这一部分)。比如说,我们更改ul
或li
的color
或font-size
时也会更改标记的color
和font-size
。为了达到两者的区分,往往需要在HTML中做一些结构上的调整,比如列表项用一个子元素来包裹(比如span
元素或::before
伪元素)。
更了大家更易于理解::marker
的作用,我们在上面的示例基础上做一些调整:
.box:nth-child(odd) li {
font-size: 200%;
color: #00b7a8;
font-family: "Comic Sans MS", cursive, sans-serif;
}
.box:nth-child(even) ::marker {
font-size: 200%;
color: #00b7a8;
font-family: "Comic Sans MS", cursive, sans-serif;
}
代码中的具体作用不做介绍,很简单的代码,但效果却有很大的差异性,如下图所示:
很神奇吧!在浏览器中查看源码,你会发现使用::marker
和未使用::marker
的差异性:
虽然::marker
易于帮助我们控制标记的样式风格,但有一点需要特别注意,如果显式的设置了list-style-type: none
时,::marker
标记内容就会丢失不可见。在这个时候,不管是否显式的设置了::marker
的样式都将会看不到。比如:
大家是否还记得,在::marker
还没有出现之前,要对列表项设置别的标记符,比如Emoji。我们就需要通过别的方式来完成,最为常见的是修改HTML的结构,或者借助CSS伪元素::before
和CSS的content
属性,例如下面这个示例:
事实上,CSS的::marker
和伪元素::before
类似,也可以通过content
和attr()
一起来控制Marker标记的效果。需要记住,生成个性化Marker标记内容需要做到几下几点:
- 非列表项
li
元素需要显式的设置display:list-item
(内联列表项需要使用display: inline list-item
) - 需要显式设置
list-style-type
为none
- 使用
content
添加内容(也可以通过attr()
配合data-*
来添加内容)
li::marker {
content: attr(data-emoji);
}
::marker
伪元素自从可以使用content
来添加内容之后,让我们可操作的空间更大了。对于列表标记(即,带有Marker标记)的元素再也不需要额外的通过::before
伪元素和content
来生成标记内容。而且,我们还可以结合计算数器相关的特性,让列表标记可造性空间更大。如果你感兴趣的话,请继续往下阅读。
::marker与计数器的结合
对于无序列表,或者说统一使用同样的标记符,那么::marker
和content
结合就可以解决。但是如果面对的是一个有顺列表,那么我们就需要用到CSS计数器的相关特性。
先来回忆一下CSS的计数器相关的特性。在CSS中计数器有三个属性:
counter-reset
:设置一个计数器,定义计数器名称,用来标识计数器作用域counter-set
:将计数器设置为给定的值。它操作现有计数器的值,并且只在元素上还没有给定名称的计数器时才创建新的计数器counter-increment
:用来标识计数器与实际关联元素范围,可接受两个值,第一个值必须是counter-reset
定义的标识符,第二个值是可选值,是一个整数值(正负值都可以),用来预设递增的值
以及两个相关的函数:
counter()
:主要配合content
一起使用,用来调用定义好的计数器标识符counters()
:支持嵌套计数器,如果有指定计数器的当前值,则返回一个表示这些计数器的当前值的串联字符串。counters()
有两种形式counters(name, string)
和counters(name, string, style)
。通常和伪元素一起使用,但理论上可以支持<string>
值的任何地方使用
一般情况之下,
counter-reset
、counter-increment
和counter()
即可满足一个计数器的需求。
CSS的计数器使用非常的简单。在元素的父元素上显式设置:
body {
counter-reset: section
}
使用counter-reset
声明了一个计数器标识符叫section
。然后再需要使用计算器的元素上(一般配合伪元素::before
)使用counter-increment
来调用counter-reset
已声明的计数器标识符,然后使用counter(section)
来计数:
h3::before {
counter-increment: section
content: "Section " counter(section) ": "
}
下图会更详尽一些,把计数器可能会用的值都罗列出来了,可供参考:
回到我们的列表设置中来。::marker
还没有得到浏览器支持之前,一般都是使用CSS的计数器来实现一些带有个性化的有顺序列表,比如下面这样的效果:
也可以借助计数器做一些其他的效果比如:
更为厉害的时,CSS的计数器配合复选框或单选按钮还可以做一些小游戏,比如@una教程中向我们展示的一个效果:
是不是很有意思,只不过我们这篇文章不是来介绍CSS计数器的。如果你从未接触过CSS计数器相关的特性而且又对这方面知识感兴趣的话,那建议你阅读完下面的内容之后回过头来阅读下面几篇有关于CSS计数器的文章:
- CSS Counters
- 如何从CSS生成内容和计数组件中得到益处
- 使用CSS的
counter-increment
做的游戏 - A Guide to CSS counter
- Counting With CSS Counters and CSS Grid
- Fun Times with CSS Counters
有关于CSS计数器相关的特性暂且搁置。我们回到::marker
的世界中来。
::marker
配合content
可以定制个性化Marker标记风格。借助CSS计数器,可以更轻易的构建带有顺序的Marker标记。同样可以让Marker标记和内容分离。更易于实现可定制化的样式风格。
接下来,我们来看一个简单的示例,看看::marker
生成的标记符和以往生成的标记符效果上有何差异没。
结果很简单,这里使用的是一个无序列表:
<ul>
<li>
Item1
<ul>
<li>Item 1-1</li>
<li>Item 1-2</li>
<li>Item 1-3</li>
</ul>
</li>
<li>Item2</li>
<li>Item3</li>
<li>Item4</li>
<li>Item5</li>
</ul>
你可以根据自己的爱好来选择标签元素。先来看::before
和content
配合counter()
和counters()
的一个效果:
// counter()
.box:nth-child(1) {
ul {
counter-reset: item;
}
li {
counter-increment: item;
&::before{
content: counter(item);
// ...
}
}
}
// counters()
.box:nth-child(2) {
ul {
counter-reset: item;
}
li {
counter-increment: item;
&::before{
content: counters(item, '.');
// ...
}
}
}
对于上面的效果,大家可能也猜到了。我们再来看一下::marker
的使用:
// counter()
.box:nth-child(3) {
ul {
counter-reset: item;
}
li {
counter-increment: item;
}
::marker {
content: counter(item);
// ...
}
}
// counters()
.box:nth-child(4) {
ul {
counter-reset: item;
}
li {
counter-increment: item;
}
::marker {
content: counters(item, '.');
// ...
}
}
你在Firefox68+ 或Safari11.1+的浏览器查看,可以看到::marker
和前面::before
效果是一样的:
另外使用::marker
还有一特殊之处。不管是列表元素还是设置了display:list-item
的非列表元素,不需要显式的使用counter-reset
声明计数器标识符,也无需使用counter-increment
调用已声明的计数器标识符。它可以直接在::marker
伪元素的content
中使用counter(list-item)
或counters(list-item, '.')
。
但是非列表元素,哪怕是设置了display:list-item
,直接在::marker
的content
中使用counters(list-item, '.')
所起的效果和我们预期的有所不同。如果在非列表元素的::marker
的content
中使用counters()
达到我们想要的效果,需要使counter-reset
先声明计数器标识符,然后counter-increment
调用已声明的计数器标识符(回归到以前::before
的使用)。具本的可以看下面的示例代码:
::marker {
content: counter(list-item);
padding: 5px 30px 5px 12px;
background: linear-gradient(to right, #f36, #f09);
font-size: 2rem;
clip-path: polygon(0% 0%, 75% 0, 75% 51%, 100% 52%, 75% 65%, 75% 100%, 0 100%);
border-radius: 5px;
color: #fff;
text-shadow: 1px 1px 1px rgba(#09f, .5);
}
.box:nth-child(2n) ::marker {
content: counters(list-item, '.');
}
.box:nth-child(3) {
section {
counter-reset: item;
}
article {
counter-increment: item;
}
::marker {
content: counters(item, '.');
}
}
是不是觉得::marker
非常有意思,特别是给元素添加Marker标记的时候。换句话说,就是在定制个性化列表符号时,使用::marker
伪元素要比::before
之类的较为方便。而且::marker
是元素原生的列表标记符(::marker
)。
一旦::marker
伪元素得到所有浏览器支持之后,我们要让列表标记符和内容分离就会多了一种方案:
- 调整HTML结构
- 伪元素
::before
和content
- 伪元素
::marker
和content
前面也向大家展示了,::marker
也可以像::before
一样,借助CSS计数器属性,可以更好的实现有序列表,甚至是嵌套的列表。
小结
尽管::marker
伪元素已经纳入到了CSS Lists Module Level 3规范中,和CSS中的list-style
以及CSS计数器同在一个范围中。但让文本内容和Marker标记符分离,即采用原生的Marker标记,还是有一段路需要走。虽然从样式的形式上实现类似的效果(内容和标记分离)有一定的方案可以实现,但我们还是应该保持一颗探索和尝试的心。如果你喜欢使用::marker
的话,又想让不支持的浏览器有一个同等的视觉效果,我们可以借助@supports()
将::before
方案做为::marker
的降级方案。感兴趣的同学,不仿尝试一下。如果你想直接看效果的话,可以点击这里查看。