聊聊 CSS 的 ::marker

::marker 是一个 CSS 的另一个伪元素,有点类似于 CSS 的 ::before::after 伪元素。只不过,它常用于给列表标记框定制样式。简而言之,使用::marker伪元素,可以对列表做一些有趣的事情,在本文中,我们将深入的聊聊该伪元素。

初识 CSS 的 ::marker

::marker 是 CSS 的伪元素,现在被纳入到 CSS Lists Module Level 3 规范中。在该规范中涵盖了列表和计算数器相关的属性,比如我们熟悉的list-style-typelist-style-positionlist-stylelist-itemcounter-increment、counter-reset、counter()和counters()等属性。

CSS 中 display 设置 list-item 值之后就会生成一个 Markers 标记以及控制标记位置和样式的几个属性,而且还定义了计数器(计数器是一种特殊的数值对象 ),而且该计数器通常用于生成标记(Markers)的默认内容。

一时之间,估计大家对于Markers标记并不熟悉,但对于一个列表所涉及到的相关属性应该较为熟悉,对于一个CSS List,它可以涵盖了下图所涉及到的相关属性:

如果你对CSS List中所涉及到的属性不是很了解的话,可以暂时忽略,随着后续的知识,你会越来越清楚的。

解构一个列表

虽然我们在 Web 的制作中经常会用到列表,但大家可能不会过多的考虑列表相关的属性或使用。就 HTML语义化出发,如果遇到无序列表的时候会使用 <ul>,遇到有序列表的时候会使用 <ol>,但在有些场景(或不追求语义化的同学)会采用其他的标签元素,比如说 <div>。针对这个场景,会采用 display 设置为list-item。如此一来会创建一个块级别的框,以及一个附加的标记框。同时也会自动增加一个隐含列表项计算数器。

ulol 元素默认情况之下会带有list-style-typelist-style-imagelist-style-position属性,可以用来设置列表项的标记样式。同样的,带有display:list-item的元素生成的标记框,也可以使用这几个属性来设置标记项样式。

list-style-type的属性有很多个值:

取值不同时,列表符号(也就是Marker标识符)会有不同的效果,比如下面这个示例所示:

Demo 地址:codepen.io/airen/full/...

在 CSS 中给列表项设置类型的样式风格可以通过 list-style-typelist-style-image 来实现,但这两个属性设置列表项的样式风格会有所限制。比如要实现类似下图的列表项样式风格:

值得庆幸的是,CSS 的 ::marker 给予我们更大的灵活性,可以让我们实现更多的列表样式风格,而且灵活性也更大。

创建 marker 标记框

HTML 中的 ulol 元素会自动创建 marker 标记框。如果通过浏览器调试器来查看的话,你会发现,不管是 ul 还是 ol 的子元素 li 会自带 display:list-item 属性设计(客户端默认属性),另外会带有一个默认的list-style-type样式设置:

这样一来,它自身就默认创建了一个 marker 标记框,同时我们可以通过 ::marker 伪元素来设置列表项的样式风格,比如下面这个示例:

CSS 复制代码
ul ::marker,
ol ::marker {
    font-size: 200%;
    color: #00b7a8;
    font-family: "Comic Sans MS", cursive, sans-serif;
}

你会看到效果如下所示:

Demo 地址:codepen.io/airen/full/...

对于非列表元素,可以通过display: list-item来创建 Marker 标记,这样就可以在元素上使用 ::marker 伪元素来设置项目符号的样式。虽然通过display:list-item在形式上看上去像列表项,但在语义化上并没有起到任何的作用。

在深入探讨 ::marker 使用之前,大家要知道,元素必须要具备一个Marker标记框,对于非列表项的元素需要显式的使用 display:list-item 来创建Marker标记框

CSS的display属性是一个非常重要的属性,现在被纳入在CSS Display Module Level 3中。CSS的display属性可以改变任何一个元素的框模型。而且在Level 3规范中给display引用了两个值的语法,比如使用display: inline list-item可以创建一个内联列表项。

::marker 的基本使用

前面的小示例中,其实我们已经领略到了::marker的魅力。在列表项li中,其实已经带有Marker标记框,可以借助::marker伪元素来设置列表标记的样式。

我们先来回忆一下,CSS的::marker还未出现(或者说不支持的浏览器)时,要对列表项设置不同的样式,都是通过li上来控制(看上去继承了li上的样式)。虽然能设置列表样式,但还是具有一定的局限性,灵活度不够大 ------ 特别是当列表项标记样式和内容要区分时

CSS的::marker会让我们变得容易的多。从前面的示例中我们可以了解到, ::marker 伪元素和列表项内容是分开的 ,正因此,我们可以独立为两个部分设计不同的样式。这在以前的CSS版本中是不可能的(除非借助::before伪元素来模拟,稍后也会介绍这一部分)。比如说,我们更改ullicolorfont-size时也会更改标记的colorfont-size。为了达到两者的区分,往往需要在HTML中做一些结构上的调整,比如列表项用一个子元素来包裹(比如span元素或::before伪元素)。

更了大家更易于理解::marker的作用,我们在上面的示例基础上做一些调整:

CSS 复制代码
.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属性,例如下面这个示例:

Demo 地址:codepen.io/airen/full/...

事实上,CSS的::marker和伪元素::before类似,也可以通过contentattr()一起来控制Marker标记的效果。需要记住,生成个性化Marker标记内容需要做到几下几点:

  • 非列表项li元素需要显式的设置display:list-item (内联列表项需要使用display: inline list-item

  • 需要显式设置list-style-typenone

  • 使用content添加内容(也可以通过attr()配合data-*来添加内容)

来看一个小示例:

CSS 复制代码
li::marker {
    content: attr(data-emoji);
}

::marker伪元素自从可以使用content来添加内容之后,让我们可操作的空间更大了。对于列表标记(即,带有Marker标记)的元素再也不需要额外的通过::before伪元素和content来生成标记内容。而且,我们还可以结合计算数器相关的特性,让列表标记可造性空间更大。如果你感兴趣的话,请继续往下阅读。

::marker 与计数器的结合

对于无序列表,或者说统一使用同样的标记符,那么::markercontent结合就可以解决。但是如果面对的是一个有顺列表,那么我们就需要用到CSS计数器的相关特性。

先来回忆一下CSS的计数器相关的特性。在CSS中计数器有三个属性:

  • counter-reset:设置一个计数器,定义计数器名称,用来标识计数器作用域

  • counter-set:将计数器设置为给定的值。它操作现有计数器的值,并且只在元素上还没有给定名称的计数器时才创建新的计数器

  • counter-increment :用来标识计数器与实际关联元素范围,可接受两个值,第一个值必须是counter-reset定义的标识符,第二个值是可选值,是一个整数值(正负值都可以),用来预设递增的值

以及两个相关的函数:

  • counter() :主要配合content一起使用,用来调用定义好的计数器标识符

  • counters() :支持嵌套计数器,如果有指定计数器的当前值,则返回一个表示这些计数器的当前值的串联字符串。counters()有两种形式counters(name, string)counters(name, string, style)。通常和伪元素一起使用,但理论上可以支持<string>值的任何地方使用

一般情况之下,counter-resetcounter-incrementcounter()即可满足一个计数器的需求。

CSS的计数器使用非常的简单。在元素的父元素上显式设置:

CSS 复制代码
body {
    counter-reset: section
}

使用counter-reset声明了一个计数器标识符叫section。然后再需要使用计算器的元素上(一般配合伪元素::before)使用counter-increment来调用counter-reset已声明的计数器标识符,然后使用counter(section)来计数:

CSS 复制代码
h3::before {
    counter-increment: section
    content: "Section " counter(section) ": "
}

下图会更详尽一些,把计数器可能会用的值都罗列出来了,可供参考:

回到我们的列表设置中来。::marker还没有得到浏览器支持之前,一般都是使用CSS的计数器来实现一些带有个性化的有顺序列表,比如下面这样的效果:

也可以借助计数器做一些其他的效果比如:

Demo 地址:codepen.io/snookca/ful...

更为厉害的是,CSS的计数器配合复选框或单选按钮还可以做一些小游戏,比如 @una教程中向我们展示的一个效果

Demo 地址:codepen.io/jak_e/full/...

@kizmarh使用同样的原理,做了一个黑白棋的小游戏

是不是很有意思,有关于CSS计数器相关的特性暂且搁置。我们回到::marker的世界中来。

::marker配合content可以定制个性化Marker标记风格。借助CSS计数器,可以更轻易的构建带有顺序的Marker标记。同样可以让Marker标记和内容分离。更易于实现可定制化的样式风格。

接下来,我们来看一个简单的示例,看看::marker生成的标记符和以往生成的标记符效果上有何差异没。

结果很简单,这里使用的是一个无序列表:

HTML 复制代码
<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>

你可以根据自己的爱好来选择标签元素。先来看::beforecontent配合counter()counters()的一个效果:

CSS 复制代码
/* 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的使用:

CSS 复制代码
/* 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, '.');
        /* ... */ 
    }
}

可以看到::marker和前面::before效果是一样的:

另外使用::marker还有一特殊之处。不管是列表元素还是设置了display:list-item的非列表元素,不需要显式的使用counter-reset声明计数器标识符,也无需使用counter-increment调用已声明的计数器标识符。它可以直接在 ::marker 伪元素的 content 中使用 counter(list-item) counters(list-item, '.')

但是非列表元素,哪怕是设置了display:list-item,直接在::markercontent中使用counters(list-item, '.')所起的效果和我们预期的有所不同。如果在非列表元素的::markercontent中使用counters()达到我们想要的效果,需要使counter-reset先声明计数器标识符,然后counter-increment调用已声明的计数器标识符(回归到以前::before的使用)。具本的可以看下面的示例代码:

CSS 复制代码
::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结构

  • 伪元素::beforecontent

  • 伪元素 ::marker content

前面也向大家展示了,::marker也可以像::before一样,借助CSS计数器属性,可以更好的实现有序列表,甚至是嵌套的列表。

写在最后

虽然 ::marker 的出现允许我们为列表标记定制样式,但它也有一定的限制性,至少到目前为止是这样。比如,我们在 ::marker 伪元素上可控样式还是有限,要实现下面这样的个性化效果是不可能的:

庆幸的是,CSS 中除了 ::marker 伪元素之外,还可以使用 ::before::after 来生成内容,然后通过 CSS 来实现更具个性化的列表标记样式。如果你对这方面的内容感兴趣的话,请猛击这里

相关推荐
gqkmiss12 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃17 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰21 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye28 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm30 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生2 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互