做前端这么多年,我发现自己被问得最多的其实不是什么炫酷的布局、复杂的动画,反而是“这个字怎么调都怪怪的”、“为什么字体大小不一样”、“那个删除线和下划线怎么控制粗细”这类看着不起眼的问题。CSS里字体样式这块,知识点又多又碎,文档一翻就懂,但真上手写页面时全是坑。这篇东西我不打算写成官方文档的复读机,就按我实际开发里会用到的顺序,把字体样式真正核心的东西过一遍,顺带把那些“文档里不会写但你一定会踩”的细节都标出来。
1. 内容整体设计与思路拆解
我一开始拿到“CSS字体样式全攻略”这个题,脑子里第一反应不是列属性清单,而是先想清楚一个事:字体样式在前端开发里到底承担什么角色?往大了说,它是页面视觉层级的第一来源;往小了说,它是用户阅读体验的最后一公里。很多团队做设计规范,色板、间距、圆角都定义得清清楚楚,唯独字体这块经常是“看着差不多就行”,结果页面一上线,Windows没抗锯齿、苹果下字体偏小、正文行高挤成一团,各种糟心。
所以我这篇攻略的逻辑是:先讲清楚字体排印的底层参数,也就是字体族、大小、粗细、行高、间距、对齐这几个必修科目;然后专门挑那些“看起来简单、做起来容易翻车”的装饰类样式,比如删除线、下划线、文字描边、渐变填充,把它们的正确打开方式和兼容性边界都标出来;最后落回到实用性上,聊两行溢出截断、段落分割线、垂直水平居中这些高频高频再高频的业务场景。
整体思路就是一个字:用得上。每个知识点我都会给出能直接抄走的代码,也会解释为什么这么写,参数背后的取舍是什么。这不是给新手背的属性字典,而是给所有需要真正把页面做好看的人的一份实操手册。
适合谁来读呢?刚学完HTML和CSS基础、准备做实战页面的人可以通读一遍建立体系;工作一两年的前端可以把装饰类和截断那几节当工具书翻;就算你是做设计转前端的,这里面的行高计算和字体栈配置应该也能帮你省不少和开发沟通的成本。
2. 字体排印的核心参数逐个拆解
2.1 字体族:font-family的优先级陷阱
字体族是字体样式的地基。我见过太多人写font-family就是浏览器默认,结果页面在不同系统上渲染出来千奇百怪。真正规范的做法是写一组“字体栈”,用逗号分隔多个候选字体,浏览器会按顺序找到第一个本地存在的来用。
我的日常配置长这样:
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; }这套写法的逻辑很清楚:-apple-system和BlinkMacSystemFont是苹果系和Chrome在Mac下的第一优先,渲染出来就是系统原生字体,最省性能也最清晰;Segoe UI覆盖Windows 10/11的默认界面字体;接下来几个中文字体是给Mac和Windows的中文场景兜底。最后Arial和sans-serif是最后的底线,保证任何环境都能有一个无衬线字体垫底。
注意:中文字体设置有个容易忽略的点——
"Microsoft YaHei"一定要用引号包起来,因为字体名里有空格。类似的还有"PingFang SC"、"Helvetica Neue",凡是名字里有空格的都得加引号,这是新手最常见的坑之一。
另外强调一点,不要在正文里直接引入那种体积很大的艺术字体。如果你非要用某个特别的字体,得走@font-face加载web font,但正文场景我会谨慎评估性能。中文字体一个字符集动不动几兆,除非做的是标题或者品牌页面,否则建议正文老老实实用系统字体栈。
2.2 字体大小与行高:为什么推荐rem和数值型行高
字体大小的单位选择,直接影响页面的可维护性和响应式适配能力。px是最直观的,但它有个问题:用户如果改了浏览器默认字号,你的页面不会跟着变,这对可访问性不友好。所以我推荐用rem作为主要字号单位。
rem是相对根元素html的字体大小来计算的,默认情况下根元素是16px。实际操作中我习惯在根元素上做一个缩放定义:
html { font-size: 16px; } @media (max-width: 768px) { html { font-size: 14px; } } body { font-size: 1rem; /* 等价于 16px / 14px */ }这样做的意思是:桌面端和移动端的字号基准不同,但所有用rem的内部元素的相对比例简单清晰。比如标题是1.5rem、正文是1rem,到小屏时整站字号等比例缩小,不需要逐个元素去调,这就是rem比px强的地方。
行高推荐使用无单位的数值,这个可能很多人没深究过。line-height: 1.6的意思是行高是自身字号大小的1.6倍,它会跟着字号走。而如果写成line-height: 24px,当同一段文字里的某个inline元素撑大了字号,24px的行高就会显得挤,两侧的中文也会出现裁切。数值型行高是Web原生最健壮的方案,因为它会根据每个元素的实际字号动态计算。
中文排版的行高经验值,正文我会用1.6到1.8,轻松阅读不累;标题用1.2到1.4,紧凑一些避免占用太多垂直空间。数字和英文较多的段落可以适当用1.5,太少会让字符上下贴在一起。
2.3 字重控制:font-weight的细节与合成坑
font-weight接受的关键字和数字对应关系是:normal就是400,bold是700,还有lighter和bolder是相对继承值加减。实际开发中我们用得最多的写法是font-weight: bold或者font-weight: 700,这两个在这里等价的。
但真正的坑在于:如果你设置的font-weight值在字体文件里没有对应的字重,浏览器会自动合成一个假加粗。合成加粗在Windows上的Chrome里经常表现为“字笔画糊成一团”,特别难看。这个问题的根源是很多默认中文字体其实只有400和700两个字重,你写font-weight: 300或font-weight: 500时浏览器会在底层做算法加粗或减细,视觉效果不稳定。
我的建议:正经做产品页面,字体忙重种类受字体文件本身限制,不要超过两个级别。如果你确实需要细、常规、中、加粗这种多级字重,就要用支持可变字体的web font。可变字体可以把wght轴做成连续的,比如font-weight: 100到900任意取值都有效。不过这是进阶用法,项目里有这个需求再考虑,简单页面没这个必要。
3. 容易被忽视的文本装饰:从删除线到描边与渐变
3.1 删除线和下划线的正确控制方式
删除线这个词搜得人多,不是因为它难,而是因为用起来总差那么点意思。基础写法一行搞定:
.del { text-decoration: line-through; }但实际项目里我几乎不会只用这一行。默认的删除线是跟着字体颜色走的,颜色相同、粗细也是浏览器说了算。要做那种设计稿里精致一点的删除线价格标签,就得进一步控制它的样式:
.price-old { text-decoration: line-through; text-decoration-color: #999; text-decoration-thickness: 2px; }text-decoration-color可以单独设置线段的颜色,text-decoration-thickness控制粗细。这两个属性在主流浏览器里兼容得都很好,可以让删除线从“字上划了一道”变成“设计过的标签效果”,视觉精致很多。
下划线一样的道理。默认的text-decoration: underline贴得近、线条细,做链接的时候总觉得没那么协调。加两行:
.link { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }text-underline-offset会让下划线和文字底部拉开4px的间距,不仅更好看,还能避免下划线和中文的撇捺笔划粘在一起显得脏。我自己的习惯是链接下划线只要能用上面这种方式精细控制,就不再给一堆奇奇怪怪的border-bottom模拟了,语义更标准,键盘导航读屏识别也正常。
3.2 文字描边、渐变与阴影:三个换着用的花活
文字描边,业界主要用-webkit-text-stroke。很多只查MDN的人会忽略它,因为标准属性表里它不是特别显眼,但实际兼容性非常好,Chrome、Edge、Safari、Firefox都支持:
.outline-text { -webkit-text-stroke: 2px #333; color: transparent; }这个组合很常用:给文字描一圈2px深色边,内部填充变透明,形成空心字。设计标题、Banner、活动页时特别好用。注意color: transparent这行别省,否则内部颜色会压过描边效果。它在Web中是非标准属性,但Apple系内核和现代Firefox的实现一路都是兼容的,可以放心用。如果想要标准属性写法,可以追加-webkit-text-stroke后缀并配合text-stroke,不过后者支持面有限,实际写的时候还是要以带前缀的为主。
字体渐变是另一个高频需求。纯CSS实现文字渐变几乎成了面试必备,核心是利用background-clip: text:
.gradient-text { background: linear-gradient(90deg, #ff6a00, #ee0979); -webkit-background-clip: text; background-clip: text; color: transparent; }三行搞定。原理并不复杂:先给元素设置一个渐变背景,然后把背景裁剪到文字形状内部,最后让文字自身的颜色透明,露出底下的背景渐变。这里有几个必须注意的细节:background-clip: text一定要配合color: transparent,否则背景会被文字颜色盖住;另外-webkit-background-clip: text在Chrome和Safari里是必须带上的前缀写法,不能只写标准属性。
与渐变配套的还有text-shadow。文字的投影可以做成发光、立体、错位等效果,不过我在项目里的态度是“阴影要克制”。文字阴影最容易翻车的是中文大字号标题,阴影半径设大了会显得“脏渲染”,所以建议要么不设,要么只用在特定视觉节点。要做一个简单的阴影效果:
.title-shadow { text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.4); }x偏移2px、y偏移2px、模糊半径8px、颜色带透明度,这是一种比较日常的用法。这种阴影适合用在有图片衬底的文字上,能明显提升可读性。
4. 段落排版与高频业务场景的实操
4.1 文本对齐和缩进:别什么情况都两行居中
text-align是文章排版最基本的属性,left、center、right、justify四个值各有用途。justify两端对齐在中文正文里视觉上很整齐,但要注意最后一行是不参与对齐的,默认保持左对齐。如果最后一行只有两个字,就会出现左侧挺齐、右侧很空的情况,这在正式排版里不合理。所以做正文的话,我通常不会单独用justify,除非配合其他手段处理段尾。
关于居中的误解要澄清一下:text-align: center只能让文字在水平方向居中,它管不了垂直方向。很多新手问“怎么把文字垂直居中”,答案要看场景:
最基础的单行文字垂直居中,可以用行高等于容器高度的方式:
.btn { height: 40px; line-height: 40px; }这个技巧的原理是,当line-height等于容器高度时,文字会在行内垂直居中。注意比较关键的一点:这种方式只对单行文字有效,内容要是换行整个排版就乱了,而且标签本身需要是块级或带有合适宽高。
多行文字的垂直居中,我用Flexbox比较多:
.flex-center { display: flex; align-items: center; justify-content: center; }或者用Grid的单一属性:
.grid-center { display: grid; place-items: center; }能记住place-items: center这个简写的话,是效率最高的写法。这些方案能应对日常90%的“居中”需求。
text-indent是段落缩进的经典属性。中文排版里首行空两格是一致习惯,写法是text-indent: 2em。这里的em是相对自身字体大小的单位,也就是说无论你字号设成12px还是20px,缩进都会自动保持两个字符的宽度,这个设计很贴心。搜索里有人问“怎么调整css容器里的文本位置”,其实很大概率就是指首行缩进或者整体对齐,把text-indent和text-align组合使用就能解决一大部分问题。
处理input这类表单项的文本居中,网上搜“css中怎么把input居中”的人特别多。这里要区分两件事:是input元素本身居中,还是input里的文字(占位符、用户输入的内容)居中。如果是后者,正确的样式有:
.input-center { width: 300px; height: 40px; text-align: center; }如果用默认的text-align: left,再把input放在一个flex容器里让容器居中,那是元素居中,不是文字居中。这两种需求别混了。
4.2 段落分割线的几种实现思路
“css实现段落分割线”这个热搜词背后,可能是想实现文章内段落之间的装饰分隔,或者是“标题和横线组合在一起”的那种版式。我捋了一下,最常见的是两种。
一种是最简单的用border-bottom,给段落底部加一条线:
.paragraph-divider { border-bottom: 1px solid #e5e5e5; padding-bottom: 1.5em; margin-bottom: 1.5em; }这个写法和打印排版里的下划线分割是同一个思路,优点是极简、不用额外元素,缺点是样式比较朴素。
另一种是标题两边带横线的“中心线分隔”,像文章里的章节记号一样。这个在以前要加三个span,现在用伪元素就能解决了:
.section-title { display: flex; align-items: center; gap: 1em; white-space: nowrap; } .section-title::before, .section-title::after { content: ""; flex: 1; height: 1px; background: #ddd; }我通常拿它做文章分段效果:标题文字两边各延伸出一条横线,整体看起来就是段落中间的一个视觉锚点。这段代码里flex: 1是关键,它让两条伪元素平分剩余空间,不管标题多长都能保持两端对齐。如果只想做单侧的短横线装饰,可以把::after去掉,再给::before设个max-width,视觉上就是一小段分隔线前置在标题前面。
4.3 三行模式与两行截断:Ing文本溢出的必考场景
现在搜“三行模式的css文件”和“css 两行超出”的人特别多,这两个搜索词其实指向上同一个场景:多行文本在固定高度内显示不下,需要截断,并且用省略号收尾。
单行截断是老知识点了,固定三件套:
.single-line { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }两个核心步骤:禁止换行,超出隐藏,最后用省略号表达“这里被截了”。这三行少一个都不行,我曾经只写了前两个,结果文字直接切掉,省略号死活不出来,后来才发现text-overflow: ellipsis必须配合前两者的限制才生效。
多行截断的经典方案是-webkit-line-clamp。其实它本身不是标准属性,但所有主流浏览器都支持,所以实践中可以当它是事实标准:
.line-clamp-2 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }这四行缺一不可。display: -webkit-box把所有内容变成弹性盒排列,-webkit-box-orient: vertical让排列方向变成垂直的,-webkit-line-clamp: 2规定最多显示两行,第3行开始藏起来,overflow: hidden防止溢出。从这就能看出来“三行模式的css文件”这个东西的本质——就是-webkit-line-clamp: 3这组配置。想显示几行,把数字换成几就行。
这套写法支持从第1行开始截断,对新闻列表、卡片标题、商品简介这类场景非常实用,建议直接封装成一个.line-clamp-2、.line-clamp-3的工具类全项目复用。另外提一句,truncate对块级元素有效,对display: flex的容器需要额外处理,不要把flex容器直接拿来截断,这个是踩过后才明白的。
4.4 字间距和词间距:微调精致度的加分项
letter-spacing控制字符间距,对中文来说就是每个汉字之间的距离。设计稿里标题经常要求加个letter-spacing: 2px之类的小间距,能让标题显得疏朗一些。
.title-tight { letter-spacing: 2px; }注意letter-spacing对文本块最后一行也生效,如果遇到末尾文字被截断,首先检查是不是它导致的。同时它和text-align: justify配合时,间距会被放大得很奇怪,尽量避免两者同用。
英文的内容里还会用到word-spacing,控制单词之间的空格宽度。中文场景基本用不上,但如果你在排版多语言站点,就能体会到它在西文阅读体验里的重要性。合理的word-spacing可以让英文说明文字的节奏感更强,不会因为默认空格太紧而读起来喘不过气。
5. 字体加载性能与渲染避坑
5.1 自定义字体加载:font-display的正确姿势
做活动页或者品牌站时,用@font-face引入自定义字体是少不了的。很多人从网上下载字体文件后直接扔过来,既不压缩也不处理加载策略,结果首屏字体“闪烁”或者白屏半天。
@font-face的关键配置我认为有三个:字体格式、字体文件和font-display。字体格式现在强制用woff2,它比woff和ttf文件体积小得多,压缩率极高,主流浏览器全部支持。
@font-face { font-family: "CustomFont"; src: url("./fonts/custom-font.woff2") format("woff2"); font-display: swap; font-weight: 400; font-style: normal; }font-display: swap的值是我最常用的。它的行为是:在字体文件加载完成前,先用备用字体渲染文本,等自定义字体加载完再换回来。这样用户不会看到一片空白,但代价是可能会看到一个“字体切换”的闪烁,也就是FOUT。对中文来说,闪烁后的字体变化其实不太显眼,整体体验是利大于弊的。
还有font-weight和font-style这两个描述符要跟使用处的font-weight、font-style对应起来,否则浏览器匹配不到,就永远加载不出自定义字体。我记得第一次用多个字重的时候,引入了两个@font-face但font-family名字写一样,结果第二个字重覆盖了第一个,页面怎么调都是同一个重量,排查了十分钟才反应过来是描述符的问题。
5.2 常见的字体渲染浏览器差异
字体在不同浏览器下的渲染差异,是前端岗位最常见的“玄学”之一。Windows的Chrome会用DirectWrite做字体平滑,Mac的Safari用的是Core Text,两者对同一款字体渲染出的观感差异肉眼可见。这其实不是CSS能完全控制的,因为它发生在浏览器引擎层面。
我们能做的是尽量让字体栈跟系统挂钩,让浏览器选到最适合当前系统的渲染路径。这就是为什么前面提到的-apple-system和Segoe UI这么好用——它们本身就是为了系统渲染优化过的。另外一个小提示:给页面加font-smooth或者-webkit-font-smoothing: antialiased的写法其实在多数现代浏览器里已经被忽略或者行为多变,不用过度依赖它。
真正影响渲染的是你选的字体文件本身。同一款字体,ttf在Mac里好好的,在Windows里就发虚,这种情况我建议直接换woff2版,或者干脆放弃这款字体,用系统字体栈代替。为了一个标题的独特效果浪费所有用户的加载时间和体验,不划算。
6. 常见问题与排查技巧实录
平时帮同事看样式问题,字体相关的能占掉30%。把常见的整理成一个速查表,遇到问题直接对着看,通常能省不少时间。
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 字体不生效 | font-family名字拼错,或引号缺失 | 确认字体名正确,带空格的加引号 |
| 删除线/下划线太贴近文字 | 默认样式不精细 | 用text-decoration-thickness和text-underline-offset调整 |
| 文字渐变不显示 | 缺少color: transparent或缺少-webkit-background-clip | 补全两行关键代码 |
| 两行截断无效 | 缺display: -webkit-box | 四行代码要写全 |
| 文字垂直居中不生效 | 把text-align当成垂直居中,容器高度和行高不一致 | 单行用行高,多行用flex/grid |
| 字重加粗后发糊 | 字体文件没有对应字重,浏览器合成 | 换可变字体或只使用默认字重 |
| 自定义字体白屏 | 没设置font-display | 加font-display: swap |
设置了letter-spacing后文字被截断 | 最后一个字符的间距溢出容器 | 给容器加一点内边距或调整间距值 |
这里再说一个我实际开发中踩过最深的坑:在flex容器里做多行截断。上面提到的line-clamp方案在普通块级元素上很好用,但是我曾经把一个卡片的内容区设成display: flex; flex-direction: column,然后在里面的文本元素上做两行截断,死活不生效,省略号不出来,文字直接裁掉。查到最后发现,-webkit-box这个显示模式和flex模式在效果上不兼容,两者叠加会让截断失效。解决方案是给文本元素单独设置min-width: 0,或者把它从flex布局里拿出来,包一层块级div再截断。这个不算底层原理,纯粹是浏览器实现细节,但很实用,值得记下来。
另一个高频翻车点是line-height和padding的叠加。很多人在按钮或标签里既设行高又设上下内边距,结果文字和背景的垂直居中位置怎么看怎么偏。原因很简单:行高会占据上方一半、下方一半的空间,再叠加padding之后,文字视觉中心就往下掉了。我的经验法则是:需要垂直居中的单行元素,要么只靠line-height撑高度,要么用flex,别把这两者混在一起用。
还有一个容易被忽略的问题:中文标点和text-align: justify的配合。前两天一个同事发来一段英文和中文混排的段落,用text-align: justify之后,中文标点出现在行首,特别难看。这是因为line-break和text-justify的默认行为没有对中文标点做禁则处理。简单处理可以给段落加text-align: left,或者用text-align: justify; text-justify: inter-ideograph;让中文的标点压缩规则生效,后者在Chrome里支持得不算特别理想,所以具体看需求定。
关于字体的text-transform属性,很多人也容易理解错。text-transform: uppercase能转大写,capitalize能转首字母大写,这些对英文文本有用,但对中文不起任何作用。中文本就没有大小写概念,页面上偶尔出现英文品牌名,记得用text-transform做统一格式,比手动改源码内容要优雅得多。
最后说一个跟字体样式强相关但很多人没意识到的点:line-height会影响整个块的高度计算,这会连带影响很多布局效果。比如用vertical-align: middle让inline元素垂直对齐时,如果line-height有偏差,视觉上始终差一点。最稳的办法是让父元素和子元素的line-height保持一致,或者在需要精确对齐的图标+文字组合里,把图标撑开的高度和行高统一算清楚。这也是Flex布局成为垂直居中最主流方案的原因,简洁、可控、不需要知道具体行高值。
7. 几个可以直接复用的工具类封装
写到这里,我觉得不给大家留下点直接能复制走的东西说不过去。整理几个我个人项目中高频使用、折腾过多次之后沉淀下来的字体样式工具类。它们的共同点是短小、不依赖框架、随手就能扔进项目里用。
文本截断类:
.u-ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .u-clamp-2 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; } .u-clamp-3 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }居中类:
.u-center-flex { display: flex; align-items: center; justify-content: center; } .u-center-grid { display: grid; place-items: center; }首行缩进和段落余量:
.u-indent { text-indent: 2em; } .u-leading-loose { line-height: 1.8; }渐变文字:
.u-gradient-text { background: linear-gradient(90deg, #ff6a00, #ee0979); -webkit-background-clip: text; background-clip: text; color: transparent; }这些类名我没有用一些工程上的BEM命名,纯粹是给大家一个快速上手的脚手架。真正放项目里,建议再套一层自己团队的前缀。
在我自己负责的产品里,u-clamp-2和u-ellipsis几乎是每个列表页都会出现的角色。写完后我在笔记本上顺手记了一句:“字体样式没有标准答案,但一定有最优解。”这句话后来成了我们团队做设计走查时的一个共识。每个属性背后都有取舍,你理解了它的原理,才能在“看着差不多”和“确实好看”之间找到那条清晰的分界线。