试探列表中的::marker

发布于 大漠

CSS中的列表包括有序、无序和定义列表,不过今天要扯的是是无序列表和有序列表。回忆一下,不管是有序还是无序列表,对于样式上都包括了list-style-imagelist-style-typelist-style-position三个样式属性,而他们又能简写成list-style。至于怎么用?这里不做过多阐述,如果不清楚的可以查看官网:

##血案的起源

前几天有位同学问过一个问题,<li>元素中的子元素浮动(float:left)之后,列表的项目符号会跑到浮动元素的后面。这个问题似乎在三年前碰到过,但又有那么点不一样。先来上张图吧,一图解胜过千言万语:

lists

简单看看代码:

<ul>
    <li>
      <span class="title">我是标题</span>
      <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
    </li>
    ...
    <li>
      <span class="title">我是标题</span>
      <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
    </li>
  </ul>

对应的CSS:

*{
  margin: 0;
  padding: 0;
}
ul {
  width: 350px;
  margin: 50px auto;
  border: 1px solid green;
  list-style-position: inside;
}
li {
  border-bottom: 1px solid #ccc;
  padding: 20px 0;
  font-size: 16px;
  color: #fff;
}
li:after {
  content:"";
  display: table;
  clear:both;
}
.title {
  float: left;
  background-color: orange;
}
.content {
  background-color: #f63;
  overflow: hidden;
  display: block;
}

这只是其中一种情形,再这个问题上,再扩展一下:li中的子元素.title向右浮动,而.content不动:

.title {
  float: right;
  background-color: orange;
}

效果如下:

lists

接下来再看一个情形,li中的子元素.title向左浮动(float:left),.content元素向右浮动(float:right):

.title {
  float: left;
  background-color: orange;
}
.content {
  background-color: #f63;
  float:right;
  max-width: 80%;
}

效果如下:

lists

上面的几种情形都是在Chrome浏览器下的测试效果,其实在Firefox浏览器也存在这样的现象,只是略有一点差别而以,比如最前面的示例,在Firefox下的效果就成这样了:

lists

现在纠结了,怎么就成这样了呢?我们一起来看看。

##查找原因

这个问题纠结了我很久,最后在W3C规范中找到原因所在。

先来看看CSS2.1中这样说:

###outside The marker box is outside the principal block box. The position of the list-item marker adjacent to floats is undefined in CSS 2.1. CSS 2.1 does not specify the precise location of the marker box or its position in the painting order, but does require that for list items whose 'direction' property is 'ltr' the marker box be on the left side of the content and for elements whose 'direction' property is 'rtl' the marker box be on the right side of the content. The marker box is fixed with respect to the principal block box's border and does not scroll with the principal block box's content. In CSS 2.1, a UA may hide the marker if the element's 'overflow' is other than 'visible'. (This is expected to change in the future.) The size or contents of the marker box may affect the height of the principal block box and/or the height of its first line box, and in some cases may cause the creation of a new line box. Note: This interaction may be more precisely defined in a future level of CSS. ###inside The marker box is placed as the first inline box in the principal block box, before the element's content and before any :before pseudo-elements. CSS 2.1 does not specify the precise location of the marker box.

给我的理解,列表中的项目符号就是一个::marker,就称他是列表独有的隐式元素吧,而这个::marker具有Box Module的特性。在列表中占有自己的位置。在CSS2.1中对其讲述的不够太多,那么在CSS3中,对其做出了更详细的描述。

根据自己的理解(不知道理解的对不对),我将li绘制了一张图来阐述其::marker:

lists

看图或许更好的理解,那么我们使用一个元素来模拟::marker:

<ul class="list">
  <li><span class="title">标题</span><span class="content">内容内容内容内容内容内容内容内容</span></li>
  <li><span class="title">标题</span><span class="content">内容内容内容内容内容内容内容内容</span></li>
</ul>
<ul class="marker">
  <li><span class="marker">&#149;</span><span class="title">标题</span><span class="content">内容内容内容内容内容内容内容内容</span></li>
  <li><span class="marker">&#149;</span><span class="title">标题</span><span class="content">内容内容内容内容内容内容内容内容</span></li>
</ul>

<style>
  .marker {
    list-style-type: none;
  }
</style>

来模拟一下.title浮动之后的::marker:

ul {
  width: 200px;
  list-style-position: inside;
}
.marker {
  list-style-type: none;
}

.title {
  float: left;
  background: orange;
}
.content {
  display: block;
  overflow: hidden;
}

对比一下默认的::marker和模拟的::marker效果:

lists

上图是list-style-positionoutside时的效果,下图是list-style-positioninside时的效果:

lists

这样一来,问题所在就很明显了。既然找到根源,那么要解决就容易多了。

更新:@貘大提供了一张图,描述的更清楚,也让我更进一步理解了其中的原因:

lists

##解决方案

如果不纠结为什么会如此?仅从解决问题上来讲的话,方法很多。就我所知道的就有:

  • 修改标签
  • 自定义向左浮动元素的display
  • 使用伪类:before
  • 使用背景图像

接下来,咱位一起来看看各解决方案是怎么处理的。

###修改标签

这种方法比较简单,给浮动元素外面嵌套一个容器:

<ul>
  <li>
    <div class="box">
      <span class="title">我是标题</span>
      <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
    </div>
  </li>
  ...
  <li>
    <div class="box">
      <span class="title">我是标题</span>
      <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
    </div>
  </li>
</ul>

CSS:

<style>
  * {
    margin: 0;
    padding: 0;
  }
  ul {
    width: 350px;
    margin: 50px auto;
    border: 1px solid green;
    list-style-position: inside;
  }
  li {
    border-bottom: 1px solid #ccc;
    padding: 20px 0;
    font-size: 16px;
  }
  li:after {
    content:"";
    display: table;
    clear:both;
  }
  .box{
    display: inline-block;
    max-width: 100%;
    vertical-align: top;
  }
  .title {
    float: left;
    background-color: orange;
  }
  .content {
    background-color: #f63;
    overflow: hidden;
    display: block;
  }
</style>

此时效果如下:

lists

由于.box容器宽度为100%,至使内容断行显示,稍作修改:

.box{
  display: inline-block;
  max-width: 90%;
  vertical-align: top;
}

这里有一个较为蛋疼的问题,就是max-width不好控制,主要因为我们都不清楚::marker的宽度是多少,根据以往经验,::marker的默认宽度为1em,尝试使用calc()来计算一下:

.box{
  display: inline-block;
  max-width: calc(100% - 1em);
  vertical-align: top;
}

这样就完美了:

lists

如果你纠结calc()兼容问题或者性能问题,那只有最土的办法,在调试工具中调度:

lists

###修改子元素的display属性

既然知道li::marker会受到子元素float的影响,那么反过来思考一下,将li的默认list-style-type设置为none,然后在浮动元素.title上重置为display:list-item;

<ul>
  <li>
    <span class="title">我是标题</span>
    <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
  </li>
  ...
  <li>
    <span class="title">我是标题</span>
    <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
  </li>
</ul>

CSS:

* {
  margin: 0;
  padding: 0;
}
ul {
  width: 350px;
  margin: 50px auto;
  border: 1px solid green;
  list-style-position: inside;
}
li {
  border-bottom: 1px solid #ccc;
  padding: 20px 0;
  font-size: 16px;
  list-style-type: none;
}
li:after {
  content:"";
  display: table;
  clear:both;
}
.title {
  float: left;
  background-color: orange;
  display: list-item;
  list-style-type: disc;
}
.content {
  background-color: #f63;
  overflow: hidden;
  display: block;
}

效果如下:

lists

###使用伪类

使用伪类也是一种方案,可以使用:before配合字符编码,来生成列表符号。

* {
  margin: 0;
  padding: 0;
}
ul {
  width: 350px;
  margin: 50px auto;
  border: 1px solid green;
  list-style-position: inside;
}
li {
  border-bottom: 1px solid #ccc;
  padding: 20px 0;
  font-size: 16px;
  list-style-type: none;
}
li:after {
  content:"";
  display: table;
  clear:both;
}
.title {
  float: left;
  background-color: orange;
}
.title:before {
  content:"•";
  display: inline-block;
}
.content {
  background-color: #f63;
  overflow: hidden;
  display: block;
}

效果如下:

lists

对于无序列表,这种解决方案也算是能接受,甚至还可以配合content绘制圆点(不做过多阐述),不过换成别的项目符号,就略显吃力。

对于有序列表可以使用CSS3中的counter()

<ol>
  <li>
    <span class="title">我是标题</span>
    <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
  </li>
  ...
  <li>
    <span class="title">我是标题</span>
    <span class="content">我是描述内容,我就是描述内容,我不是别的</span>
  </li>
</ol>

CSS:

* {
  margin: 0;
  padding: 0;
}
ol {
  width: 350px;
  margin: 50px auto;
  border: 1px solid green;
  list-style-position: inside;
  counter-reset: list-item;
}
li {
  border-bottom: 1px solid #ccc;
  padding: 20px 0;
  font-size: 16px;
  list-style-type: none;
  counter-increment: list-item;
}
.title:before {
  content: counter(list-item) '.';
  display: inline-block;
}
li:after {
  content:"";
  display: table;
  clear:both;
}
.title {
  float: left;
  background-color: orange;
}
.content {
  background-color: #f63;
  overflow: hidden;
  display: block;
}

效果如下:

lists

话又说回来,无序列表使用字符编码,对于其他方面的字符不好控制,其实也可以使用counter()函数,配合其第二个值来控制:

.title:before {
  content: counter(list-item, disc);
  display: inline-block;
}

lists

变更一下,

.title:before {
  content: counter(list-item, square);
  display: inline-block;
}

效果如下:

lists

也就是说,只要使用counter()配合其第二个参数值(参数值选择list-style-type),就可以类似控制列表一样控制其项目符号。

有关于counter()相关的介绍可以猛击这里

###使用背景图片

使用背景图片,我认为是不尽人意的一种方案,如果列表项目符号不是圆,需要额外制作图像,特别是有序列表的时候,会让你抓狂。在这个方案中也抛弃了有序列表的解决。

另外,此处示例使用的是gradient来模拟圆点:

li {
  border-bottom: 1px solid #ccc;
  padding: 20px 0;
  font-size: 16px;
  list-style-type: none;
  background: radial-gradient(circle at center, #000 10%, #000 20%, #fff 30%,transparent 100%);
  background-size: 20px 20px;
  background-repeat: no-repeat;
  background-position: 0 19px;
  padding-left: 20px;
}

效果如下:

lists

##未来解决方案

在CSS3提出了::marker规范。一旦浏览器支持这个规范,我们就可以像类似使用:before这样的伪类来控制项目列表符。来看一个简单的示例:

<ol>
  <li>This is the first item.</li>
  <li>This is the second item.</li>
  <li>This is the third item.</li>
</ol>

CSS:

li { list-style-type: lower-roman; }
li::marker { margin: 0 3em 0 0; color: blue; font-weight:bold; }

对应的效果如下:

lists

到目前为止,你是还看不到效果的。你懂得。

根据上例推断,那么我们就可以给::marker设置浮动样式。如此一来,就不会跑到浮动的子元素后面了。试目以待。以后也不需要纠结了。

##总结

本文从一个血案引起对::marker的初探。从而借助列表的::marker特性,暂时采用几种不同的方案解决了问题。不过这几种方案都是换汤不换药,我们也只能拭目以待::marker到来。