text-transform 是一个可以将文字进行大小写转换或单词首字符大写的 CSS 属性。自从知道了它,我就开始偷懒不在 js 中去在调用相关的转换函数了。

昨天在给多说扩展用户 UA 功能时遇到一个 text-transform: capitalize 失效的问题,经过一番测试,找到了问题的原因。

场景还原

根据当时场景,抽取关键 css 属性:

.capitalize {
text-transform: capitalize
}
.before:before {
content: 'hello'
}
.after:after {
content: 'world'
}

现有如下的 HTML,你们猜最终会是什么结果?

<h1 class="capitalize">hello world</h1>
<h1 class="capitalize">HELLO WORLD</h1>
<h1 class="capitalize before">world</h1>
<h1 class="capitalize after">hello</h1>

结果:

Hello World
HELLO WORLD
Helloworld
Helloworld

问题DEMO链接:text-transform: capitalize 失效demo

问题解析

通过运行结果可以发现全小写单词组合时,转换没有任何问题,略过。

大写单词组合

全大写单词组合,转换失效?真的是这样吗?

通过查阅相关文档,关于 capitalize 的介绍为:将每个单词的第一个字母转换成大写
看来是我们对它期望太高了,text-transform: capitalize并不能像 lodash 库中的转换函数那样,将全大写的的 AAA 转换成你期望的 Aaa

含有伪元素

这也是我面临到的问题,每个 UA 标签上的 tootip 都是我用伪类模拟的。
由于当时伪元素是绝对定位的,在视觉上真的是当作了另外的元素看待。

而在问题 DEMO 中,我们还原了伪元素默认显示的位置::before 就是在原始内容前追加内容,:after 就是在原始内容后追加内容。
由于最后生成的内容之间没有间隙,导致 text-transform 转换时将其当作一个单词。

为了验证我的结论,我在含有伪元素的内容前或后加了个空格:

<h1 class="capitalize before"> world</h1>
<h1 class="capitalize after">hello </h1>

最后转换成功:

Hello World
Hello World

DEMO

解决方案

虽然找到了问题的原因,但是实际场景中使用伪元素的初衷就是要将一个元素当作多个元素使用,HTML 中加空格会有半个中文字符宽度的空白,也就等于产生了不必要的边距。

做到这里真是感觉不如直接写个 js 转换函数算了。

function capitalize(string) {
return string.charAt(0).toUpperCase() + string.substr(1).toLowerCase()
}

写出来后忽然发现这段代码完全可以用 CSS 去“实现”。

.capitalize {
text-transform: lowercase;
}
.capitalize:first-letter {
text-transform: uppercase;
}

上个月旭哥博客有篇很详细的 :first-letter 介绍:张鑫旭:深入CSS :first-letter伪元素及其实例等

其中提到了 :first-letter:before 之间的猫腻,就是说 :first-letter 也生效于 :before 伪元素。
不过我测试后发现,:before 伪元素设置为绝对定位时,:first-letter将只作用于原始内容

:first-letter和:before示例demo

利用这个机制,总算可以给自己一个完美的答复了。

.capitalize {
text-transform: capitalize
}
.before {
padding-left: 2.5em
}
.before:before {
position: absolute;
left: 0;
content: 'hello'
}
.before:first-letter {
text-transform: uppercase
}
.after:after {
position: absolute;
content: 'world';
text-transform: capitalize
}
  • 附加内容在 :before 时,使用 :first-letter + uppercase 解决,如果原始内容有大写,需要再给原标签加 lowercase
  • 附加内容在 :after 时,给 :after 再加 capitalize

DEMO

End