最近在工作中遇到了一个 white-space 和 float 组合时,CSS 样式在 Firefox 浏览器下不兼容的问题,特此记录一下。需求是做一个国家列表,类似于下面这样:

country-list.png

需求很简单,任何一个 CSS 初学者应该都会做,我毫不犹豫的使用 ul 实现如下:

<div class='container'>
    <ul>
        <li>中国</li>
        <li>中国香港</li>
        <li>中国澳门</li>
        <li>中国台湾</li>
        <li>美国</li>
        <li>英国</li>
        <li>日本</li>
        <li>加拿大</li>
        <li>法国</li>
        <li>韩国</li>
        <li>德国</li>
    </ul>
</div>

并写下相应的样式:

.container {
    width: 200px;
    background: #fff;
    border: 1px solid #37c249;
}
.container ul {
    list-style: none;
    padding: 0 0;
    margin: 0 0;
}
.container ul li {
    padding-left: 5px;
    border-bottom: 1px dashed #e7e7e7;
    height: 30px;
    line-height: 30px;
}
.container ul li:hover {
    cursor: pointer;
    background-color: #37c249;
}

写完之后,在不同的浏览器里都测试一遍,没毛病,于是提交代码,收工。

好景不长,第二天测试找过来说,有的国家名太长了,譬如这个:圣多美和普林西比民主共和国,样式超出了列表宽度,掉到了下一行,而且把下一行的文字覆盖了。如下图所示:

country-list-over.png

我一拍脑袋,嗯,都是我的错,确实没考虑周全,我赶紧修复一下。像这种文字长度超出边界的情况,解决方案有很多,最常见的方法莫过于将超出部分隐藏,并在后面加点点点来显示。关于这种解决方案,张鑫旭的这篇博客给出了很多种实现方法(文章比较老,其中他所说的 Firefox 不支持 text-overflow,现在的浏览器基本上都已经支持了)。

于是我加了三行代码,所谓的“三连击”,像这种通用的解决方案,类似于一种设计模式,应该熟练运用在项目里。

.container ul li {
    padding-left: 5px;
    border-bottom: 1px dashed #e7e7e7;
    height: 30px;
    line-height: 30px;
    
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

其中 white-space: nowrap 表示超过文字部分不要换行,然后使用 overflow: hidden 将超出部分隐藏起来,再加上 text-overflow: ellipsis 打造出点点点的效果,如下图所示:

country-list-nowrap.png

看上去效果不错,在不同浏览器中测试一遍,没毛病,于是再次提交代码,收工。

第三天,产品找到我,说这个国家列表除非国家名长点,大多数情况下国家名就两三个字,右边部分有很多空白,看上去有点单调,而且不够国际化,想调整下样式。多年开发经验的我早就对产品需求的善变见怪不怪,于是按捺住内心的情绪,平淡的说,好的,没问题。拿到最新的样式如下所示:

country-list-with-en.png

哦,原来是在右边加个国家英文名,看上去果真高大上不少,内心一边默默的佩服产品脑洞大开的思路,一边偷偷窃喜,就这点改动,能难倒我?于是抄起编辑器,刷刷刷,在每个国家后面加上英文名,代码如下:

<div class='container'>
    <ul>
        <li>中国<span>China</span></li>
        <li>中国香港<span>Hongkong,China</span></li>
        <li>中国澳门<span>Macao,China</span></li>
        <li>中国台湾<span>Taiwan,China</span></li>
        <li>美国<span>United States of America</span></li>
        <li>英国<span>United Kingdom</span></li>
        <li>日本<span>Japan</span></li>
        <li>加拿大<span>Canada</span></li>
        <li>法国<span>France</span></li>
        <li>韩国<span>Korea</span></li>
        <li>德国<span>Germany</span></li>
    </ul>
</div>

在每一个 li 节点中,加了一个行内元素 span,并将该国家对应的英文名放在里面。原先的宽度肯定不够,需要调宽一点,改成了 350px,右边加上 5px 的 padding,然后将 span 元素浮动到右边,样式代码如下:

.container {
    width: 350px;
    background: #fff;
    border: 1px solid #37c249;
}
.container ul {
    list-style: none;
    padding: 0 0;
    margin: 0 0;
}
.container ul li {
    padding: 0 5px;
    border-bottom: 1px dashed #e7e7e7;
    height: 30px;
    line-height: 30px;
    
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.container ul li:hover {
    cursor: pointer;
    background-color: #37c249;
}
.container ul li span {
    float: right;
}

在 Chrome 中刷新测试了一下,完美,这个时候正好到了饭点,信心满满的我直接提交了代码,其他浏览器就不看了,肯定没问题的。

下午,我正在为自己高效的工作效率,神乎其技般的码字能力感到得意时,测试找过来了。你一上午都做了啥?说好的国家英文名呢?咋啥都没有?我一听,怎么可能?上午明明做好了啊。跑到测试那一看,Firefox 浏览器,还真的啥都没有!只是把宽度调宽了点。

country-list-with-en-firefox.png

这是怎么回事,难道上午写的三行代码竟然有问题?这让我不禁开始怀疑人生。郁闷的回到工位,打开 Chrome(63.0) 浏览器一看,没问题,打开 IE11 和 Edge,也没问题,最后打开 Firefox(57.0)还真的有问题!一直以来都是在修补 IE 浏览器的各种不兼容,这一次竟然轮到 Firefox 了。

F12 打开开发者工具,查看页面元素,span 节点有,只是不显示而已,那么肯定是被隐藏了。那么为什么被隐藏了?看代码只有 overflow: hidden 这一种可能,将这个样式去掉,国家英文名果然露出来了,只是,所有的国家英文名都掉到下一行:

country-list-with-en-firefox-show.png

但是我明明设置了 white-space: nowrap 不允许换行啊,为什么会换行呢?然后尝试着将 white-space: nowrap 这条样式去掉,出乎意料的竟然显示正常了。

难道被我找到了一个 Firefox 的 Bug?赶紧在 Google 上搜索相关的信息,果然在 stackoverflow 上找到了一个和我的情况完全一样的问题:Firefox float bug? How do I get my float:right on the same line? 但是,并没有人说这是 Firefox 的 Bug,而是提出了三个解决方法。

解决方法一:调换浮动元素和非浮动元素的位置

将浮动的 span 元素放在文本的前面,如下:

<div class='container'>
    <ul>
        <li><span>China</span>中国</li>
        <li><span>Hongkong,China</span>中国香港</li>
        <li><span>Macao,China</span>中国澳门</li>
        <li><span>Taiwan,China</span>中国台湾</li>
        <li><span>United States of America</span>美国</li>
        <li><span>United Kingdom</span>英国</li>
        <li><span>Japan</span>日本</li>
        <li><span>Canada</span>加拿大</li>
        <li><span>France</span>法国</li>
        <li><span>Korea</span>韩国</li>
        <li><span>Germany</span>德国</li>
    </ul>
</div>

解决方法二:white-space: normal

white-space: nowrap 修改为 white-space: normal,或者去掉 white-space 的样式(默认为 normal):

.container ul li {
    padding: 0 5px;
    border-bottom: 1px dashed #e7e7e7;
    height: 30px;
    line-height: 30px;
    
    white-space: normal;
    overflow: hidden;
    text-overflow: ellipsis;
}

解决方法三:将非浮动元素改成浮动元素

将中文名放在一个 span 元素中,英文名放在另一个 span 元素中,然后将中文名向左浮动,英文名向右浮动即可。

<div class='container'>
    <ul>
        <li><span class='l'>中国</span><span class='r'>China</span></li>
        <li><span class='l'>中国香港</span><span class='r'>Hongkong,China</span></li>
        <li><span class='l'>中国澳门</span><span class='r'>Macao,China</span></li>
        <li><span class='l'>中国台湾</span><span class='r'>Taiwan,China</span></li>
        <li><span class='l'>美国</span><span class='r'>United States of America</span></li>
        <li><span class='l'>英国</span><span class='r'>United Kingdom</span></li>
        <li><span class='l'>日本</span><span class='r'>Japan</span></li>
        <li><span class='l'>加拿大</span><span class='r'>Canada</span></li>
        <li><span class='l'>法国</span><span class='r'>France</span></li>
        <li><span class='l'>韩国</span><span class='r'>Korea</span></li>
        <li><span class='l'>德国</span><span class='r'>Germany</span></li>
    </ul>
</div>

样式如下:

.container ul li span.l {
    float: left;
}
.container ul li span.r {
    float: right;
}

虽然问题解决了,但是问题的原因并没有找到。只是隐隐觉得 Firefox 对 white-space: nowrap 的实现和其他内核的浏览器的实现应该不一样。white-space 有 5 种可能的属性,如下图所示(图片来源):

white-space-css.png

可以看出 nowrap 和 normal 唯一的区别是:是否允许换行。将 white-space 换成其他三种值发现,pre 一样有换行问题,而 pre-wrap 和 pre-line 都没有问题。那么为什么允许换行的情况下,浮动元素都正常显示在当前行,而不允许换行的情况,浮动元素却掉到了下一行?

关于这个问题我并没有找到答案,要完全搞清楚这一点,我觉得有必要去仔细翻阅一下 CSS 的规范文档以及了解不同浏览器内核在渲染元素时对 white-space 样式处理上的区别(Gecko、Webkit、Trident),精力有限,我到这里就停止了,并没有继续研究。如果你有兴趣,欢迎深挖下去,一定可以发现更精彩的内容。

我只是有一个猜想:Firefox 在处理 white-space: nowrap 元素时,认为该元素不可换行,那么如何让一个元素里的内容不换行呢?可能是设置了其宽度为无限宽,这样就导致了浮动元素在这一行没有多余的位置,只能下移被挤到另一行了。欢迎讨论。

总结

CSS 对于一个后端开发人员来说,无异于一场噩梦,我所认识的大多数后端开发人员,都不太愿意接触前端技术,特别是 CSS 以及不同浏览器的兼容问题,觉得这是没有多少技术含量的事。我在机缘巧合下,有幸参与到公司的前端开发工作,在工作过程中学到了不少前端的技巧和技术。我对最近几年前端技术的迅速发展感到非常吃惊,可能大多数后端开发都不知道,前端技术日新月异,早已不是当年 jQuery 一把梭的年代了,无论是 Angular、React、Vue 等等前端框架的变迁,还是 Node.js、Webpack 等前后端一体化的趋势,更不用说 ECMAScript6、HTML5、CSS3 这些最新的技术都让我感到不可思议。CSS 对我们来说,只是一种类似于 HTML 的标记语言,但是要真正学好这门语言,并不是仅仅掌握一些 CSS 语法就够了,而是要深入到不同浏览器的内核,探索浏览器渲染元素的原理,这条路漫长而充满挑战,与君共勉。

注:本文情节纯属虚构。

参考

  1. 关于文字内容溢出用点点点(…)省略号表示
  2. white-space - CSS | MDN
  3. Firefox float bug? How do I get my float:right on the same line?
扫描二维码,在手机上阅读!