
做前端这几年我越来越觉得CSS不是“能不写就不写”的边角料而是决定页面质感的主战场。尤其当你在一个看起来功能完整的页面上反复调字号、行距、对齐时才会明白字体和文本属性这几十个不显眼的属性几乎就是“高级感”的来源。这是我在前端开发系列里的第三篇上一篇讲完了CSS选择器和样式引入方式这一篇我们集中把font和text这套“常用元素属性”掰开揉碎字体怎么选、字号怎么定、行高怎么设、对齐怎么排、省略号怎么做、渐变色怎么画全部围绕“从字体到文本”这一条线展开。不管你是刚学会写选择器的新手还是做完两三个项目但总觉得页面不够精致的前端新人这篇都能帮你真正把文字这层皮做到位。篇幅会偏长建议先收藏再往下读。1. 字体属性把“默认字体”变成“设计字体”的第一道关卡1.1 font-family这行代码决定了页面性格font-family是浏览器渲染文字时首先会查的属性。很多新人写字体时喜欢只给一个名字就收工比如font-family: 微软雅黑;结果在Mac上打开系统找不到微软雅黑直接落回默认中文字体有时候是苹方有时候是宋体。页面一旦变成宋体气质立刻倒退十年。这不是浏览器傻而是你没有把候选字体列全。一个合格的前端写字体栈要像写路由表一样依次给出候选方案。我常用的全局字体栈是这样html { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, PingFang SC, Microsoft YaHei, sans-serif; }这里面的逻辑并不复杂-apple-system和BlinkMacSystemFont让macOS和iOS优先用San Francisco系列Segoe UI是Windows 10以上系统的默认无衬线Roboto 主要覆盖Android设备PingFang SC是苹果生态的中文中文字体Microsoft YaHei就是微软雅黑。这样不同系统打开页面都能优先看到自家平台上最舒服的字体。最后加一个sans-serif兜底意思是前面全都找不到时让浏览器选一个默认无衬线来渲染。这里有几个细节要记住。第一中文字体名如果包含空格或者由多个单词组成一定要加引号比如PingFang SC、Microsoft YaHei、Hiragino Sans GB。第二font-family的查找是从左到右找到第一个可用的字体就停不会帮你“智能地挑选好看的那个”所以顺序就是优先级。第三字体名不能写错尤其是拼音型字体拼错一个字母整个栈都白搭浏览器会直接跳过它。实操心得我会把全局字体定义放在html而不是body上这样所有后续元素都天然继承这个字体族统一性更好。如果某个组件要换字体再单独覆盖font-family。另外遇到“为什么我的页面字体和设计稿不一样”的问题先别急着改代码打开DevTools的Computed面板拉到Font列表看看当前元素实际渲染的是哪个字体再回头检查字体栈的顺序往往一下就能找到原因。1.2 font-sizepx、rem、em、%之间的真实关系很多人从入门起就习惯用px设置字号比如font-size: 14px。px在屏幕上的确稳定一像素就是一像素设计稿也特别好对。但它的短板也很明显这是绝对单位用户如果在浏览器设置里调大默认字号你的页面不会跟着变。在移动端做适配时px也无法随着视口宽度自动变化。所以现在项目里更适合的做法是把px、rem、em配合使用。rem是相对于html根元素的字号。假设浏览器默认根字号是16px那么1rem 16px0.875rem 14px。em则是相对于当前元素自身的字号但因为字号会从父级继承所以嵌套层级越多em的缩放就越容易累积出“复利效应”。举个例子html { font-size: 16px; } .parent { font-size: 0.875em; } /* 浏览器按父级16px计算得到14px但会覆盖父级字号 */ .child { font-size: 0.875em; } /* 实际是 12.25px而不是14px因为基准变成14px了 */这不是一个容易一眼看穿的结果所以我的建议是全局字号和组件的基础尺寸用rem局部需要跟着当前字号一起缩放时用em比如按钮的padding、图标大小、badge文字。不要在大范围嵌套里用em控制正文字号否则到后面调一处全乱除非你真的在做那种“整体等比缩放”的特殊场景。现在做响应式字体我更推荐clamp()font-size: clamp(14px, 2vw 10px, 20px);这行的意思是基础值2vw 10px在视口宽度变化时跟着变但结果小于14px就取14px大于20px就取20px。这种写法在窄屏手机上不会小到看不清在宽屏显示器上也不会大到撑破布局。注意如果你打算用rem做无障碍优化最好不要把html的font-size写死成固定px。用户在浏览器的“字体大小”设置里调整的是默认字号浏览器会把根字号一起放大。你写死之后这个机制就失效了。所以更稳妥的做法是基础要就留在默认16px或者用百分比设置根字号比如font-size: 100%然后正文全部用rem。1.3 字重和字型不是设置了100到900就都有对应字体font-weight支持从100到900的数值也支持normal和bold。难点在于字体文件里不一定真的包含9套字重。对中文网页来说大多数系统中的字体只有regular和bold两档你脑袋里想着“细一点”写在CSS里是font-weight: 300浏览器找不到300的字重可能会直接用400渲染或者用算法强行把400压细效果往往会发虚。尤其在小字号屏幕上这种“伪细体”的观感很差。所以如果你需要真正细字重的标题比如苹方Light、思源黑体Light最好通过font-face加载对应字重的字体文件并明确font-weight范围。或者使用可变字体variable font一个文件包含整个连续字重轴不过在中文网页里可变字体的文件体积和兼容性还都是现实约束不是所有项目都能上。font-style就简单一些只有normal、italic、oblique。中文没有真正的italic字体浏览器强行把正体倾斜一下反而会破坏笔画结构。所以我一般建议中文正文别开斜体如果是引用英文单词或书名用italic是符合语义的。font-variant最常用的是small-caps能把英文小写字母渲染成小号大写。这个属性对中文没用适合英文品牌、缩写这类场景。如果你在做英文内容展示可以用它制造一点“仪式感”但在中文页面里基本可以忽略。1.4 行高单行垂直居中的秘密也是一个继承陷阱行高在font相关属性里属于特殊存在因为它直接影响文本的垂直排布。最推荐的写法是不带单位body { line-height: 1.6; }不带单位的值意思是“当前元素font-size的1.6倍”。它的最大优势是继承时不会把事情搞复杂。子元素会拿到“1.6”这个数字然后基于自己的font-size重新计算行高。如果你写成line-height: 160%继承的却是父级已经算好的“具体像素值”再传给子元素。一旦子元素字号变大行高却还是基于父级算出的值就很容易出现上下文字挤在一起或者间距不平衡。单行垂直居中是很多初学者的第一个坑。容器高度固定成40px里面只有一行文字直接把line-height也设成40px视觉上就居中了。这个方法简单有效适合按钮、标签、提示条。但要注意英文里如果包含y、g、p这种下部突出的字母视觉重心会略偏下精确一点可以用flex布局配合align-items: center。我现在写按钮仍然习惯用line-height因为代码更短但如果按钮内部有图标或者文本需要换行还是得老老实实上flex。多行文本的行高计算同样容易翻车。比如一个固定高度80px的卡片字号14px行高1.6单行行高22.4px理论上能塞三行多一点。如果忘记算卡片padding第三行可能就溢出或者顶到边缘视觉上很挤。计算时永远把padding包含进去养成习惯。1.5 font 综合速写一行代码的取舍如果页面某处需要同时设置斜体、字重、字号、行高、字体族可以合并成一行font: italic 700 16px/1.5 PingFang SC, Microsoft YaHei, sans-serif;顺序规则很死板font-style、font-variant、font-weight这三个可选的放在最前面顺序可交换中间必须写font-size如果需要行高就在字号后面加/行高最后必须写font-family。字号和字体族不可省略其他都可以省。这个简写最大的坑是“重置”效应。font会把你没有提到的属性全部重置为默认值比如font-stretch、font-size-adjust。如果你在前面定义过这些冷门属性然后再写一次font缩写它们全部会静默归零。所以在项目里我的习惯是只有在一个全新文本组件需要完整定义字体属性时才使用font简写日常只是把字号从14px改成16px就直接写font-size: 16px不要动缩写。2. 文本属性让文字在画面里“各就各位”2.1 text-align、text-indent、text-transform基础但决定精致度text-align看起来简单但.justify这个值最容易踩坑。它会让一行文字两端对齐等宽拉开间距。中文排版里如果一行只有几个字中间的空白会被拉伸得特别大像是字和字之间突然插入了空格阅读体验非常难受。移动端屏幕窄一行的字数本来就少我基本不会在正文里用justify。如果你确实需要两端对齐可以配合text-align-last: left让最后一行不强制撑开。text-indent是首行缩进通常用来做段落开头空两格。单位选em最合适比如text-indent: 2em;这样首行缩进正好是两个字宽和字号大小无关。用px的话一旦字号变化缩进就失配。这个属性还可以玩一些花活比如把负值缩进和overflow: hidden组合起来隐藏列表默认符号但我更建议不要这样“借”文本属性容易出兼容问题。text-transform负责大小写转换包括uppercase、lowercase、capitalize。它只改变视觉呈现不影响DOM里的实际内容所以对屏幕阅读器和SEO来说文本原文是什么还是什么。中文场景下主要用在英文文案、品牌名、价格标签上把产品型号统一转大写这样前端代码里写iPhone还是IPHONE视觉上都是统一的大写后端要改文案也方便。2.2 letter-spacing、word-spacing、white-space控制文字间的呼吸感先说letter-spacing也就是字间距。很多好看的标题其实不是字体本身多特别而是你把字间距拉开到0.05em或者0.1em整个字就“贵”了起来。注意这里我推荐用em而不是px因为字间距应该跟随字号变化。中文字体如果字间距太大会断断续续像单独的字块所以一般正文不要设置超过0.02em标题可以适当放宽。word-spacing是词间距它只作用于英文、数字空格等可以分词的场景对中文几乎无效。不要指望用它在中文里增加字和字的空隙那个是letter-spacing的活。white-space决定源码里的空格和换行怎么处理。默认normal会把多个空格压缩成一个也会让文本在容器边界自动换行。nowrap表示一行装到底不换行这个常和省略号一起用。pre保留源码中的空格和换行类似pre标签适合展示代码、日志文本。这里有一个常见误用想保留换行又不想让超长文本溢出更多人需要的是pre-wrap它保留空格和换行但允许在必要时换行。pre-line则只保留换行压缩空格。这几个值的区别可以按需求对号入座。2.3 文本装饰与阴影下划线也能玩出细节text-decoration是传统的下划线、删除线、上划线。完整写法现在拆成了text-decoration-line、text-decoration-color、text-decoration-style、text-decoration-thickness。比如做链接下划线时想要一条偏下的细线a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }text-underline-offset可以控制下划线和文字的距离这个参数在很多UI设计里是点睛之笔直接决定链接看起来是“贴上去”还是“浮在上面”。另外如果想去掉下划线别只用display: inline这类手段直接用text-decoration: none最干净。text-shadow是给文字加阴影写法是四个参数依次为x偏移、y偏移、模糊半径、颜色。多组阴影用逗号分隔可以模拟描边、发光、甚至立体的效果。示例.title { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 8px rgba(255, 255, 255, 0.5); }不过阴影也不能乱加尤其正文里加一个若有若无的投影会让文字在浅色背景上发灰。文本描边则更偏向特效-webkit-text-stroke能做到比如做一个“空心的文字标题”但它是非标准属性而且会覆盖字体的笔画细节适合当氛围元素不适合当正文。2.4 文本超出一行省略号的标准姿势这是开发中使用频率最高的三个属性组合我直接给模板.ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }三个属性缺一不可white-space: nowrap让文字不换行overflow: hidden让超出的部分不可见text-overflow: ellipsis在裁切处显示省略号。这个写法只适用于单行文本。多行省略号老牌的写法是.multiline-ellipsis { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }这个写法虽然带-webkit-前缀但目前在Chrome、Safari、Edge、Firefox里都支持实际项目中足够用。它的核心是-webkit-line-clamp控制显示行数中间的-webkit-box-orient: vertical让元素变成垂直方向的弹性容器。唯一要注意的是容器需要设置宽度否则现代浏览器会根据自己的content宽度计算换行结果容易出现省略号出现位置不符合预期。关于省略号还有一个实战技巧如果你希望省略号前面显示“查看详情”这样的固定尾巴不要用复杂的字符串拼接直接用伪元素覆盖在右上角或者用flex的min-width: 0加内联样式让省略号的文本容器自适应。这个场景在移动端列表里经常出现后面可以单独开一篇专门讲。3. 高颜值进阶字体渐变、自定义字体与细节打磨3.1 文字渐变与背景裁剪标题不再单调文字渐变是现在我给页面标题“上妆”最常用的手法。它的核心并不是某个字体属性而是把background-clip设为text让背景色只渲染在文字形状内部同时把文字本身颜色设为透明.gradient-text { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }这段代码需要在意两个兼容点background-clip: text的标准写法虽然已经在现代浏览器普及但不少版本还是要求带-webkit-前缀所以两个都要写另外color: transparent只有在支持background-clip: text时才能让文字“透出”背景渐变如果浏览器不支持文字会直接消失这很危险。做好降级很简单用supports包一层.title { color: #764ba2; } supports (background-clip: text) or (-webkit-background-clip: text) { .title { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; background-clip: text; color: transparent; } }这样不支持的浏览器至少能看到一个纯色标题。实际使用中渐变文字最适合Logo、主标题、数字强调还可以配合背景图片做“图片文字”比如让一张风景照出现在文字内部。注意背景图片不要选得过于花哨否则文字识别度急剧下降。文字描边效果也可以和渐变搭配但-webkit-text-stroke会让笔画变细某些字体在浅色底上反而看不清。我一般只是用它做1px以内的极细描边模拟一种扁平设计感。3.2 font-face 与 woff2把选中的字体“装”进网页如果系统自带的字体无法满足视觉要求就得用font-face加载真正的自定义字体。基本结构不复杂font-face { font-family: MyDisplayFont; src: url(fonts/MyDisplayFont.woff2) format(woff2), url(fonts/MyDisplayFont.woff) format(woff); font-weight: 400; font-style: normal; font-display: swap; }这里有几个关键点。首先是字体格式现代浏览器已经全面支持woff2它的压缩率最好应该作为首选woff作为兼容备选ttf文件体积大能不用就不用。其次是font-display我建议设置swap。它能让文字先以系统默认字体渲染等自定义字体加载完成后再替换避免页面出现长时间空白。但swap也有代价字体替换瞬间可能会引起布局轻微跳动如果页面对这种跳动敏感可以使用optional让浏览器根据网络情况快速决定是否加载这个字体但这需要按项目评估。真正的大坑在中文自定义字体。一个完整的中文字体文件动不动就是几MB甚至十几MB直接普通加载整个font-face页面性能会爆炸。解决办法有两个方向一是用字体子集化工具把页面里用到的几百个字抽成一个很小的字体文件很多生成器的思路都是这个二是干脆不要全站用自定义中文标题字体只在少数几个重要数字、LOGO上用甚至把文字做成SVG图片。前端开发里“性能优先于完美”是一条铁律尤其在移动端。font-face的字体名也要小心不要和系统已有字体重名。你自定义的字体如果叫PingFang SC会和macOS系统字体产生命名冲突。我之前见过有人把字体命名为“HarmonyOS Sans”结果在某些设备上加载出现古怪的渲染。给自定义字体起一个足够特殊的前缀比如MyFE-Display能从源头避开很多奇怪问题。3.3 vertical-align行内元素对齐的精细调节vertical-align是行内排版的小瑞士军刀也是很多新人搞不懂为什么图片底部总多一块空隙的根源。它只对行内元素、内联块级元素和表格单元格生效对块级元素里的普通文本没有直接作用。最常见的用法是让图片和文字对齐.icon { vertical-align: middle; }middle会让元素的中线对齐父级基线上方半个x的高度这个解释起来很绕但视觉效果就是更自然。我们经常看到img底部会多出几像素空白那是因为默认是基线对齐而基线下面还有降部空间。用block的图片则没有这个问题或者你也可以把img的vertical-align: middle设置上空隙就消失了。vertical-align的其他值还包括top、bottom、text-top、text-bottom适合做装饰性图标跟随文字顶线或底线对齐。数字和中文混排时用vertical-align: baseline可能不太舒服我会改为middle视觉更稳。表格单元格里vertical-align: middle则是实现单元格内容垂直居中最好的方式。这里有一个经典坑两个inline-block元素之间为什么总有间隙间隙来自HTML源码换行所产生的空白字符解决方法是父元素font-size: 0再给子元素设置回文字字号。但这样会影响整体可维护性如果只是为了对齐优先检查是不是vertical-align设置不当造成的“假间隙”再决定是否重置字号。4. 踩坑实录字体冲突、渲染差异与单位迷思4.1 为什么我写的font-family在别人电脑上变了样这是前端工位出现频率最高的问题之一。设计稿里的字体是造字工房你项目里写了一模一样的font-family结果在同事电脑上打开发现变成系统默认黑体。原因就是对方电脑上没有安装这个字体。font-family只是给浏览器一个“心愿列表”如果找不到就会继续往后的备用字体走最后用系统兜底。解决这个问题的思路有三种。第一使用系统字体栈接受不同平台间的显示差异把页面做好的关键是选好回退字体比如中文页面在Mac上显示苹方、在Windows上显示微软雅黑这种差异可以接受。第二把设计稿里的特殊字体通过font-face打包进网页任何人打开都能看到一样的效果代价是性能和加载成本。第三通过DevTools检测打开开发者工具选中元素在Computed面板检查font-family的实际计算值看看当前到底落到哪个字体上再针对性调整fallback顺序。这里我还想提醒一个细节本地测试时如果开发者的电脑安装了设计稿里的字体页面看起来很正常但用户电脑没有就会回退到备选字体导致样式“看起来不对”。所以在本地验收样式时可以临时用无痕模式或者切换浏览器user agent来模拟其他系统或者直接让设计稿字体走web font方案保证两端稳定。4.2 中文字体、系统字体与缓存兼容性背后的真相中文字体在CSS里的处理比英文字体更敏感。首先中文字体名称要用引号包住尤其类似“Microsoft YaHei”这种带空格的名称不引号可能导致解析异常。其次中文网页的字体文件体积天生比英文大因为常用汉字至少两三千个做web font时要做好子集化否则一次请求几十万字体的场景会非常酸爽。还有一个容易忽略的问题是字体缓存。自定义字体通过font-face加载之后在用户浏览器里是有缓存的。如果你改了字体文件内容但文件名没变旧用户可能还看到旧字体反复反馈“没更新”。解决办法是给字体文件加版本参数url(fonts/my-font.woff2?v20250120)或者让构建工具在打包时自动在文件名里加hash。这和后端清缓存的思路一样只是很多前端新手容易漏掉。中文字体之间的“冲突”也值得留意。有些系统预装了仿宋、楷体、黑体等许多字体如果你的CSS里写了font-family: 仿宋, 楷体, serif;在不同系统上可能得到完全不同的字形。更稳妥的做法是明确指定某一个字体并且接受它不存在时回退到serif而不是sans-serif否则字体会变得不伦不类。实际项目中常用中文字体栈我会建议直接沿用我前面给出的系统栈把特殊字体留给少数需要强调的元素。4.3 单位迷思文本排版中rem、em、%、vw怎么选单位问题在文本排版里尤其容易混乱。我做一个简单对照表方便查阅单位参照物适用场景注意px设备像素设计稿精确对齐、边框、投影用户浏览器字号缩放不生效remhtml根元素字号全局正文字号、间距、组件尺寸根字号被写死时失去无障碍意义em当前元素或父级字号按钮内边距、局部相对缩放嵌套过多会叠加难预估%父级元素对应属性宽度、高度、行高百分比用于字号时等效于emvw/vh视口宽度/高度全屏布局、响应式大标题极端窄屏下可能过小需要clampvw用在文本字号上有一种很爽的响应式效果比如font-size: 5vw文字大小会跟随视口宽度变化。但问题是在超宽屏上字会大到失控在很窄的折叠屏上又会显得局促。所以更稳的写法是配合clamp()我前面已经提过font-size: clamp(14px, 2vw 10px, 22px)这种式子能同时提供下限和上限。关于无障碍有一条值得深入理解浏览器里的“字体大小”设置本质上是修改默认字号而这个默认字号会传导到html根元素的16px这个基准上。如果你在代码里把根元素font-size写死成14px用户怎么调都没有用如果根元素保持100%或者不写那整个rem体系就能跟着用户偏好缩放。所以现在项目中我写全局文字大小已经很少用px了正文全部走rem特殊参数走clamp这样既稳定又更包容。最后再说一个很多人踩过的坑line-height使用百分比的继承问题。如果父级line-height: 160%它会被计算成具体像素值再被子元素继承子元素字号一大行高就可能偏小甚至重叠。而line-height: 1.6继承的是一个数字子元素会基于自己字号重新算行高这是最可靠的。写行高的时候尽量养成用无单位数字的习惯耐得住继承链的考验。