第11章 文本与字体属性

第十一章:文字与字体属性

文字是网页的灵魂,字体是文字的外衣。学会控制文字和字体,你的网页就不再是"千篇一律的系统默认字体",而是独具特色的品牌视觉。

11.1 font-family 字体

11.1.1 字体栈——“Arial”, “Helvetica”, sans-serif,浏览器从左到右依次查找,找到可用字体就停止

1
2
3
4
5
6
7
8
9
/* 字体栈:浏览器会依次尝试每个字体,直到找到可用的 */
body {
  font-family: "Arial", "Helvetica", sans-serif;
}

/* 常见字体栈 */
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: "Georgia", "Times New Roman", serif;

写字体栈时有几条硬规则:

  • 字体名含空格(或中文名)必须加引号,如 "Microsoft YaHei"。写成 Microsoft YaHei 是无效声明,整条 font-family 会被丢弃——这是很常见却难发现的错误。
  • 最后一定要放一个通用字体族(serif、sans-serif、monospace、cursive、fantasy、system-ui),它是"保底",保证前面的字体全都没有时仍然有合理的默认外观。
  • 不要用通用字体族当普通字体名加引号:"sans-serif" 会被当成一个叫 sans-serif 的真实字体去查找,找不到就继续往下,等于白写。正确写法是不加引号:sans-serif。
  • 字体栈的顺序反映优先级:越靠前越优先使用,而"具体字体名"应放在"通用族"之前。

字体族分为两大类:通用族(serif 衬线、sans-serif 无衬线、monospace 等宽、cursive 手写、fantasy 装饰)和具体字体名(Arial、PingFang SC 等)。一个稳妥的跨平台栈可以这样写:

1
2
3
4
5
/* 先用系统自带的现代无衬线字体,最后落到通用族 */
body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto,
               "PingFang SC", "Microsoft YaHei", sans-serif;
}

11.1.2 中文字体——“Microsoft YaHei”(微软雅黑)、“PingFang SC”(苹方)、“SimSun”(宋体)

1
2
3
4
/* 中文网页常用字体栈 */
body {
  font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Heiti SC", sans-serif;
}

11.2 font-size 字号

11.2.1 px——绝对单位,不随用户的字号设置变化

1
2
3
4
5
6
7
8
/* 像素是最常用的绝对单位 */
.text {
  font-size: 16px;
}

h1 {
  font-size: 32px;
}

这里有一个经常被误解的点:px 在 CSS 里是"绝对单位",指的是它不会随用户在浏览器里调整默认字号(比如把默认 16px 改成 20px)而自动变化——这一点和 rem、em 不同。但是浏览器的页面缩放(Ctrl/⌘ + +)会等比放大整个页面,px 写的大小当然也会跟着变大。

换句话说:

  • 用户改"设置 → 字体大小"→ 只有 rem/em 会跟着变,px 不变;
  • 用户按 Ctrl/⌘ + + 缩放页面 → 所有单位(含 px)一起变大。

无障碍实践建议:正文字号用 rem,让用户的字号偏好生效;只在确实需要严格像素对齐的场合(图标、细边框)才用 px。

11.2.2 em——相对于父元素"计算后的"字体大小,嵌套时容易失控

1
2
3
4
5
6
7
.parent {
  font-size: 20px;
}

.child {
  font-size: 1.5em;  /* 20px * 1.5 = 30px */
}

关键细节:1em 等于当前元素继承来的、父元素最终计算出的 font-size,而不是父元素写在 CSS 里的那个声明。如果父元素自己也是 em,效果就会一层层放大:

1
2
3
4
/* 很多人在这里踩坑 */
.level1 { font-size: 1.2em; }  /* 相对 body(16px)→ 19.2px */
.level2 { font-size: 1.2em; }  /* 相对 19.2px → 23.04px */
.level3 { font-size: 1.2em; }  /* 相对 23.04px → 27.65px */

三层嵌套后字号已经比预期大了一倍多。所以:

  • 需要"可随父元素缩放"的场景(按钮内文字、图标旁文字)才用 em;
  • 页面级排版用 rem,避免连锁放大。

11.2.3 rem——相对于根元素(html)的字体大小,推荐用于响应式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
html {
  font-size: 16px;
}

.text {
  font-size: 1rem;  /* 16px */
}

.title {
  font-size: 2rem;  /* 32px */
}

11.2.4 clamp()——clamp(16px, 2vw, 24px) 实现流体字体,文字在最小值和最大值之间随视口平滑缩放

1
2
3
4
/* 流体字体:最小 16px,最大 24px,中间随视口平滑缩放 */
.fluid-text {
  font-size: clamp(16px, 2vw, 24px);
}

11.2.5 ch——字符"0"的宽度,用于限定输入宽度

1
2
3
4
/* 用于输入框最小宽度设置 */
.username-input {
  min-width: 20ch;
}

1ch 的定义是当前字体中数字"0"的宽度,而不是任意字符的宽度。既然"0"在多数等宽(monospace)字体里和所有其他字符一样宽,ch 在等宽场景下非常精确;但在比例字体(如中文正文、Arial 等)里,不同字符宽度差别很大,ch 只能作为"大约能放多少个数字"的粗略估计。

常见用法正是上面的输入框/验证码框:20ch 大致等于"能显示 20 个数字/字母的宽度"。另外要注意 ch 也和 em 一样会随字号缩放——字号变了,20ch 的像素宽度也会变。


11.3 font-weight 字重

11.3.1 关键字——normal(等于 400)、bold(等于 700)

1
2
3
4
5
6
7
.text-normal {
  font-weight: normal;  /* 等于 400 */
}

.text-bold {
  font-weight: bold;      /* 等于 700 */
}

normal 与 400、bold 与 700 完全等价,只是写法不同。另外还有两个相对值:lighter(比父元素轻)和 bolder(比父元素重),但它们按"从父元素字重往相邻档位跳"计算,实际结果依赖父元素取值,容易出乎意料,不如直接写数字清晰。

重要现实:不是每个字体都提供全部 9 档字重。很多中文字体只有 400 和 700 两档,此时你写 500 或 600,浏览器要么就近取 400/700,要么合成假粗体。

11.3.2 数值——100、200、300、400、500、600、700、800、900

1
2
3
4
5
6
7
8
9
.font-thin { font-weight: 100; }
.font-extralight { font-weight: 200; }
.font-light { font-weight: 300; }
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.font-extrabold { font-weight: 800; }
.font-black { font-weight: 900; }

11.4 font-style 和 font-variant

11.4.1 font-style——normal、italic(斜体)、oblique(倾斜)

1
2
3
.normal { font-style: normal; }
.italic { font-style: italic; }    /* 斜体,使用专门的斜体字形 */
.oblique { font-style: oblique; }  /* 倾斜,强制倾斜文字 */

11.4.2 font-variant——small-caps(小型大写字母)、normal

1
2
.small-caps { font-variant: small-caps; }  /* 小型大写字母 */
.normal { font-variant: normal; }

11.4.3 font-synthesis——控制浏览器是否"伪造"粗体/斜体

1
2
3
4
/* 禁止浏览器合成字体样式 */
.no-synthesis {
  font-synthesis: none;
}

当一个字体只提供了常规字重(400),而你又要求 font-weight: 700 时,浏览器不会报错,而是把 400 的字形"描粗一点"来冒充粗体——这就是合成(synthesis)。它能让页面不至于变成纯常规体,但假粗体/假斜体通常比真正的粗体字、真斜体难看得多。font-synthesis: none 就是明确告诉浏览器"宁可保持原样,也别伪造"。

可选值:

值含义
none禁止合成(等价于 font-synthesis-weight: none; font-synthesis-style: none; font-synthesis-small-caps: none;)
weight允许合成粗体
style允许合成斜体(oblique)
small-caps允许合成小型大写字母

规范演进提醒:早期 font-synthesis 只有 none/weight/style 三个值;CSS Fonts Module Level 4 把它细化为 font-synthesis-weight、font-synthesis-style、font-synthesis-small-caps 三个长属性,简写语法为 none | [ weight || style || small-caps ]。此外还有一个不在简写范围内的 font-synthesis-position(控制上下标是否允许合成),需要单独声明。新代码可以优先用拆分后的长属性,控制更精细。


11.5 font-optical-sizing 光学尺寸

11.5.1 auto(自动调整)/ none——让可变字体按字号切换"最佳形态"

1
2
3
4
5
6
7
.auto-sizing {
  font-optical-sizing: auto;  /* 默认值,自动优化 */
}

.no-optical-sizing {
  font-optical-sizing: none;
}

同一款字体,用来做标题(很大)和用来做正文(很小)时,理想的笔画粗细和字距其实不一样:小字号需要更粗、更开的笔画才看得清,大字号则需要更细腻。**光学尺寸(optical sizing)**就是字体里的一条 opsz 轴,专门用来做这种自适应。

font-optical-sizing: auto(默认)让浏览器根据当前 font-size 自动调整这条轴;none 则关掉这种自动调整。注意两点:

  • 只有当字体本身是带 opsz 轴的可变字体时才有可见效果,普通静态字体上它什么也不做;
  • 这是"自动"行为,不需要你手动设置 font-variation-settings: "opsz" 14——但如果你确实手动设置了,手动值会覆盖自动值。

11.6 font-feature-settings OpenType 特性

11.6.1 控制连字、字距等高级 OpenType 特性

1
2
3
4
/* 连字(liga)、字距调整(kern)等 */
.text-with-features {
  font-feature-settings: "liga" 1, "kern" 1, "swsh" 1;
}

font-feature-settings 是一把"万能钥匙",用 4 字符的 OpenType 标签直接开关字体里的特性。常见标签:

标签作用
"liga"标准连字(如 fi 合成一个字形)
"dlig"自由连字
"kern"字距调整
"tnum"表格数字(数字等宽,便于对齐金额、表格)
"pnum"比例数字(默认,数字宽度不一)
"onum"旧式数字(高低错落,适合正文)
"smcp"小型大写字母
"swsh"花体/装饰字形(swash)

值 1 表示开启、0 表示关闭。但优先使用语义化的高层属性:font-kerning、font-variant-ligatures、font-variant-numeric、font-variant-caps 等,它们可读性更好、也能被 font-variant 简写覆盖;只有在高层属性没有覆盖到的冷门特性(如 "swsh")上才用 font-feature-settings。

实践中最常用的一例是让表格里的数字对齐:

1
2
3
4
5
/* 数字等宽 + 表格数字,金额小数点对齐不跳动 */
.price {
  font-variant-numeric: tabular-nums;
  /* 等价于 font-feature-settings: "tnum" 1; */
}

注意:不是所有字体都包含上述所有特性,字体里没有的标签写了也没有效果。


11.7 font-variant-alternates 替代字形

11.7.1 在 @font-feature-values 中定义字形集后使用

1
2
3
4
5
6
7
@font-feature-values "MyFont" {
  @swash { swash-variant: 1; }
}

.flowers {
  font-variant-alternates: swash(swash-variant);  /* 使用上面定义的字形变体 */
}

11.8 -webkit-text-size-adjust 移动端字号

11.8.1 防止 iOS 横屏时自动放大字号——请用 100%,而不是 none

1
2
3
4
/* 正确做法:把自动调整限制在 100%,只阻止"额外放大" */
html {
  -webkit-text-size-adjust: 100%;
}

背景:iOS Safari 在横屏、或页面很宽时,会猜测这是"桌面版网页",于是自动把正文字号放大,避免文字太小。这会让精心设计的排版在横屏突然变形。-webkit-text-size-adjust 用来关闭这种推测。

但千万不要写 -webkit-text-size-adjust: none。 在部分浏览器/系统上,none 会连带禁止用户手动双指缩放页面,这属于严重的无障碍问题(低视力用户离不开缩放)。写 100% 的含义是"不要替我放大,但允许用户自己放大",效果与 none 接近而不会误伤用户。

补充:text-size-adjust 目前仍需 -webkit- 前缀,写标准属性 text-size-adjust: 100% 是为将来做准备,两者一起写最稳妥。

11.8.2 auto——允许浏览器调整字号(默认)

1
2
3
.auto-adjust {
  -webkit-text-size-adjust: auto;  /* 默认值 */
}

11.9 line-height 行高

11.9.1 无单位写法——line-height: 1.5,推荐写法

1
2
3
4
/* 推荐:无单位写法,行高是字号的倍数 */
.text {
  line-height: 1.5;  /* 推荐! */
}

11.9.2 有单位写法——line-height: 20px 或 line-height: 150%

1
2
3
4
5
6
7
8
/* 有单位写法 */
.px-height {
  line-height: 20px;
}

.percent-height {
  line-height: 150%;
}

无单位、px、百分比三者最关键的差别在继承行为上,这也是"为什么推荐无单位"的真正原因:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 父元素字号 16px */
.parent {
  font-size: 16px;
  line-height: 150%;   /* 计算成 24px 后再继承下去 */
}

.child {
  font-size: 32px;
  /* 继承到的是固定的 24px,不是 150%!
     32px 的字配上 24px 行高 → 文字挤在一起甚至重叠 */
}

对比一下:

写法继承时传递的是子元素改了字号会怎样
line-height: 1.5(无单位)数字 1.5 本身行高 = 子元素字号 × 1.5,自动跟着变
line-height: 24px固定的 24px不跟着变,可能过小/过大
line-height: 150%固定的计算值 24px不跟着变(和 px 一样)

所以规则很简单:需要继承的场景一律用无单位写法,百分比和 px 只在个别不想被继承的地方使用。

11.9.3 单行垂直居中——设置 line-height 等于容器高度

1
2
3
4
5
/* 单行文字垂直居中 */
.centered-text {
  height: 60px;
  line-height: 60px;
}

这个技巧的前提是单行:容器只有一行文字,把行高撑成容器高度,文字自然就被居中。一旦文字换成两行,第二行就会溢出容器——所以更现代的写法是给容器用 display: flex; align-items: center;,能同时适配多行。另外 line-height 过大时文字会有额外的上下留白,视觉上未必"正中"(因为字体上下本来就留有余量),需要微调时可用 padding 配合。


11.10 text-align 文本对齐

11.10.1 left(默认)、center、right、justify(两端对齐)

1
2
3
4
.left { text-align: left; }
.center { text-align: center; }
.right { text-align: right; }
.justify { text-align: justify; }  /* 两端对齐,英文常用 */

11.11 vertical-align 垂直对齐

11.11.1 对行内级元素和表格单元格有效,对块级元素无效

1
2
3
4
/* vertical-align 对行内级元素有效 */
.inline-element {
  vertical-align: middle;
}

vertical-align 决定了元素在它所在的那一行(line box)里相对基线怎么摆放。因此它只对"处于行内格式化上下文中的元素"生效,具体包括:

  • 行内元素(display: inline,如 span、a);
  • 行内块(display: inline-block,如内联按钮、内联图片容器);
  • 行内表格(display: inline-table);
  • 表格单元格(display: table-cell,此时语义略有不同)。

它对块级元素无效。所以 div { vertical-align: middle; } 不会有任何效果——这是初学者最常见的困惑之一。如果你想让块级元素垂直居中,应该用 Flexbox(align-items)、Grid,或前面的 line-height 技巧,而不是 vertical-align。

11.11.2 常用值——baseline、middle、top、bottom

1
2
3
4
.baseline { vertical-align: baseline; }
.middle { vertical-align: middle; }
.top { vertical-align: top; }
.bottom { vertical-align: bottom; }

11.11.3 图片底部有空隙——设 vertical-align: middle 或 bottom 解决

1
2
3
4
/* 图片底部空隙问题解决 */
img {
  vertical-align: bottom;
}

为什么图片底部会有空隙?因为图片默认 vertical-align: baseline,而基线的位置是按文字的"下缘"定的——文字下方还留有给字母下伸部(如 g、y)的空间,图片就被顶起来了,看起来像多了一条缝。

彻底一点的做法有三种:给图片设 vertical-align: bottom(或 middle/top)、把图片改成 display: block、或者干脆让父容器用 Flex 布局。第一种改动最小,第三种最"现代"。


11.12 text-decoration 文本装饰

11.12.1 text-decoration-line——underline(下划线)、line-through(删除线)、overline(上划线)

1
2
3
.underline { text-decoration-line: underline; }
.line-through { text-decoration-line: line-through; }
.overline { text-decoration-line: overline; }

11.12.2 text-decoration-color——装饰线颜色

1
2
3
4
.colored-line {
  text-decoration-line: underline;
  text-decoration-color: red;
}

11.12.3 text-decoration-style——solid、dashed、dotted、wavy

1
2
3
4
.solid { text-decoration-style: solid; }
.dashed { text-decoration-style: dashed; }
.dotted { text-decoration-style: dotted; }
.wavy { text-decoration-style: wavy; }

11.12.4 缩写——text-decoration: underline wavy red;

1
2
3
4
/* 缩写写法 */
.decorated {
  text-decoration: underline wavy red;
}

关于 text-decoration 还有两个常被忽略的点:

第一,它不能被子元素"取消"。 给链接整体加下划线后,即使给里面的 span 写 text-decoration: none,下划线依然存在——因为装饰线是父元素画的,会贯穿子元素:

1
2
3
4
5
6
a { text-decoration: underline; }
a span { text-decoration: none; }  /* 无效!下划线还在 */

/* 正确做法:把装饰线加在真正需要的那个子元素上 */
a { text-decoration: none; }
a span { text-decoration: underline; }

第二,装饰线可以微调位置和粗细,用 text-underline-offset 和 text-decoration-thickness:

1
2
3
4
5
6
.link {
  text-decoration: underline;
  text-underline-offset: 4px;          /* 下划线上下偏移 */
  text-decoration-thickness: 2px;      /* 下划线粗细(也可用 from-font、百分比) */
  text-decoration-skip-ink: auto;      /* 下划线遇到字母下伸部自动断开(默认) */
}

这几个属性在做"悬停时下划线滑出"之类的高级效果时非常有用。text-decoration 简写不包含 text-underline-offset 和 text-decoration-thickness,它们需要单独写。


11.13 text-transform 文本转换

11.13.1 uppercase(全大写)、lowercase(全小写)、capitalize(首字母大写)

1
2
3
.uppercase { text-transform: uppercase; }    /* 全大写 */
.lowercase { text-transform: lowercase; }    /* 全小写 */
.capitalize { text-transform: capitalize; }  /* 首字母大写 */

三点必须知道:

  • 它只改变"显示效果",不改变数据。 表单里输入 hello,套上 text-transform: uppercase,用户看到的、提交时传给后端的仍是 hello。如果业务要求"必须是大写",服务端还要自己做转换,别依赖 CSS。
  • capitalize 是按"每个单词的首字母"处理的,而单词的划分依赖语言规则。它不会把小写中间的大写"压平",iPHONE 首字母本来就大写,结果是 iPHONE 而不是 Iphone。英文人名/品牌名有特殊大小写时,别指望 capitalize。
  • 选择器匹配的是原始文本。 text-transform: uppercase 之后,.uppercase::first-letter 之类的伪元素、以及无障碍朗读读到的都是原始大小写,搜索页面内容时也按原文匹配。

无障碍提示:全大写(uppercase)的文字对阅读障碍用户来说更吃力,屏幕阅读器有时还会把连续大写字母逐个字母念出。重要正文尽量不要整段大写。


11.14 文字溢出处理

11.14.1 单行省略——overflow:hidden + text-overflow:ellipsis + white-space:nowrap

1
2
3
4
5
.single-line-ellipsis {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

11.14.2 多行省略——line-clamp(旧写法为 -webkit-line-clamp)

1
2
3
4
5
6
7
8
/* 标准属性和兼容写法一起写,现阶段最稳妥 */
.multi-line-ellipsis {
  line-clamp: 3;
  display: -webkit-box;      /* 现阶段仍需配合 -webkit-box 才能生效 */
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

多行省略的实现一直有点历史包袱:-webkit-line-clamp 原本是 WebKit 私有属性,因为太好用被各浏览器广泛支持,后来规范才加入标准的 line-clamp。现在写代码时把两者都写上:

属性状态
display: -webkit-box + -webkit-box-orient: vertical让元素变成"垂直多行盒子",line-clamp 的前提
-webkit-line-clamp: 3最广泛支持的实现,Chrome/Safari/Firefox 都认
line-clamp: 3标准属性名,用时不带前缀
overflow: hidden隐藏被截断的后续行,必须写

注意副作用:display: -webkit-box 会覆盖原本的 display 值,如果你本来设的是 display: flex 或 grid,加了多行省略后布局会变,需要额外测试。另外多行省略通常还要配合 overflow-wrap: break-word,否则超长英文单词撑不开容器,省略号可能不出现。


11.15 white-space 空白处理

11.15.1 normal——多个空格合并为一个(默认)

1
2
3
.normal-whitespace {
  white-space: normal;
}

11.15.2 nowrap——不换行,所有空白合并

1
2
3
.nowrap {
  white-space: nowrap;
}

11.15.3 pre——保留原始格式,像 pre 标签一样

1
2
3
.pre-format {
  white-space: pre;
}

white-space 其实是两个问题的组合:要不要合并空白(空格、换行、制表符),以及允许在哪儿换行。把取值列全,理解会清楚很多:

值连续空格是否自动换行行尾换行符典型用途
normal合并为一个允许当空格忽略普通正文(默认)
nowrap合并为一个禁止当空格忽略单行省略、不换行的按钮文字
pre原样保留禁止形成换行需要严格保留格式且不换行
pre-wrap原样保留允许形成换行展示代码、聊天消息(最常用)
pre-line合并为一个允许形成换行只尊重换行、不保留多个空格
break-spaces原样保留允许形成换行像 pre-wrap,但行尾空格也占位、能撑开换行

最实用的两个:展示用户输入的文本用 pre-line(尊重换行、脏空格自动清理),展示代码用 pre-wrap(格式一点不变,且长行会自动折行)。


11.16 其他文字属性

11.16.1 text-indent——首行缩进

1
2
3
.indent {
  text-indent: 2em;  /* 首行缩进,2em = 2 × 当前元素字号 */
}

11.16.2 letter-spacing——字符间距

1
2
3
.spaced {
  letter-spacing: 2px;
}

11.16.3 word-spacing——单词间距

1
2
3
.word-spaced {
  word-spacing: 5px;
}

11.16.4 overflow-wrap / word-wrap——长单词换行

1
2
3
4
5
/* 防止长单词/URL撑破容器 */
.break-word {
  overflow-wrap: break-word;  /* 标准属性,推荐 */
  word-wrap: break-word;     /* 兼容旧版浏览器 */
}

word-wrap 是 overflow-wrap 的旧名(属于"别名",不是独立属性),两者写法效果相同。常见的几个相关属性容易混淆,这里一次说清:

属性/值行为什么时候用
overflow-wrap: normal只有整个单词都放不下时才在别处换行,不会切断单词默认
overflow-wrap: break-word单词实在放不下时才强行断词最常用,处理 URL/长英文
overflow-wrap: anywhere和 break-word 类似,但断词位置会影响容器的 min-content 宽度计算配合 Flex/Grid 的自动定宽
word-break: break-all随时可以在任意字符处断行(对中文影响大)只在确实需要时用
word-break: keep-all中文/日文不在字符间断行,只在标点或空格处断需要避免中文被硬切断
hyphens: auto按语言规则加连字符换行(需要 lang 属性)英文排版讲究时

一个实用组合是让中文尽量按标点/词组换行、实在放不下时才硬断:

1
2
3
4
.card-title {
  overflow-wrap: break-word;
  word-break: keep-all;   /* 中文优先在标点或空格处换行,不逐字硬断 */
}

11.17 text-shadow 文字阴影

11.17.1 常用写法——text-shadow: 水平偏移 垂直偏移 模糊半径 颜色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* 常用文字阴影 */
.shadow {
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}

/* 多层阴影 */
.multi-shadow {
  text-shadow: 1px 1px 0 #fff, 2px 2px 4px rgba(0, 0, 0, 0.3);
}

/* 发光效果 */
.glow {
  text-shadow: 0 0 10px #ff6600, 0 0 20px #ff6600;
}

书写规则:偏移X 偏移Y [模糊半径] 颜色。只有 X、Y 是必需的,模糊半径可省略(省略即无模糊,得到硬边阴影),颜色可写在前面也可写在后面。多层阴影用逗号分隔,先写的画在最上层,所以做描边/发光时常把最"紧"的一层写在最前。

几个实用提示:

  • 阴影不占布局空间,不会撑大元素,也不会被 overflow: hidden 裁掉;
  • 给深色背景上的白色文字加 text-shadow: 0 1px 2px rgba(0,0,0,.5) 能明显提升可读性;
  • 阴影是逐字渲染的,长文本叠加多层阴影会增加绘制开销,动画里慎用。

11.18 print-color-adjust(打印样式)

11.18.1 economy(允许省墨降质)/ exact(强制保留颜色)

属性改名提醒:早期规范草案里这个属性叫 color-adjust,后来因为名字太笼统、容易和颜色调整的其他概念混淆,标准名已改为 print-color-adjust。现在写代码应当使用新名字(color-adjust 作为遗留别名仍被部分浏览器识别,但不要在新项目里用)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@media print {
  .save-ink {
    print-color-adjust: economy;  /* 允许浏览器降低颜色/背景以省墨 */
  }

  .keep-color {
    print-color-adjust: exact;                        /* 强制保留颜色和背景 */
    -webkit-print-color-adjust: exact;                /* 兼容旧版 Safari/Chrome */
  }
}

默认行为是 economy:浏览器打印时可能去掉页面背景色、把深色底改成白色,以免浪费墨水。如果你确实需要打印出品牌色(比如发票上的色块),就用 exact。注意:是否能真的打出颜色还取决于打印机和用户的"打印背景图"选项,exact 只是让浏览器别主动丢弃,不能保证最终纸张上一定有颜色。


11.19 @font-face 自定义字体

11.19.1 基本语法——src: url(“字体路径.woff2”) format(“woff2”)

1
2
3
4
5
6
7
8
@font-face {
  font-family: "MyFont";
  src: url("fonts/MyFont.woff2") format("woff2");
}

.custom-font {
  font-family: "MyFont", sans-serif;
}

完整一点的写法通常还要指定字重、字形类型,并用 local() 优先尝试用户本机已装字体,以及提供多种格式兜底:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@font-face {
  font-family: "MyFont";
  src: local("MyFont"),
       url("fonts/MyFont.woff2") format("woff2"),
       url("fonts/MyFont.woff")  format("woff");   /* 老浏览器兜底 */
  font-weight: 400;      /* 这个文件对应哪个字重 */
  font-style: normal;    /* 对应正体还是斜体 */
  unicode-range: U+0000-00FF;  /* 可选:只用于这批字符(做字体子集/中文分片) */
}

/* 同一字体的粗体是另一个文件,需要用同样的 font-family 再声明一次 */
@font-face {
  font-family: "MyFont";
  src: url("fonts/MyFont-Bold.woff2") format("woff2");
  font-weight: 700;
}

几个要点:

  • 格式优先级:现代项目首选 woff2(体积最小、压缩率最高),woff 只留给很老的浏览器。
  • format() 不能省:不写的话浏览器可能下载了才发现不支持,白白浪费一次请求。
  • font-weight/font-style 描述符一定要和文件真实内容对应。如果粗体文件没声明 font-weight: 700,浏览器就会认为"这个家族只有 400",需要用粗体时要么去合成假粗体,要么继续用常规体。
  • 中文项目建议拆分:一个完整中文字体动辄几 MB,可以用 unicode-range 把常用字分成几个子集文件,浏览器只下载页面真正用到的那一两片。

11.19.2 font-display——控制"字体还没下载完时先显示什么"

1
2
3
4
5
@font-face {
  font-family: "MyFont";
  src: url("fonts/MyFont.woff2") format("woff2");
  font-display: swap;  /* 先显示后备字体,下载完再切换 */
}

自定义字体要等网络下载,这段时间里文字用什么显示,由 font-display 决定。它有五个取值,权衡的是"文字立刻可见"和"避免闪烁":

值下载期间的显示超时后的行为适用场景
auto由浏览器决定(通常等同 block)——不明确表态
block短暂隐藏(约 3 秒"白屏期")超时后先用后备字体,字体到了再换图标字体、必须用自定义字体才对的内容
swap立刻用后备字体显示字体到了直接换(可能有可见的跳动)正文最常用,保证首屏可读
fallback只有很短的白屏期(约 100ms)100ms 内到就用,否则整页会话都用后备字体想要折中、能接受"这次不换"
optional只有很短的白屏期若下载太慢则本次访问直接放弃(可能缓存后下次用)追求极致性能、字体只是锦上添花

实践建议:

  • 正文:swap 或 optional,保证用户尽快看到内容;
  • 图标字体:block,宁可短暂隐藏也不能出现乱码方块;
  • 想减少"换字体跳动",可以给后备字体设置 size-adjust、ascent-override 等描述符做度量对齐,让切换前后行高基本一致。

本章小结

恭喜你完成了第十一章的学习!文字与字体属性是网页设计的基础。

核心知识点

属性说明
font-family字体栈;含空格的字体名必须加引号,末尾要有通用字体族
font-size字号(px 绝对、em 相对父级、rem 相对根元素、clamp 流体、ch 数字宽)
font-weight字重(100-900,normal=400、bold=700)
font-style / font-synthesis斜体;font-synthesis 控制是否允许浏览器伪造粗体/斜体
font-optical-sizing / font-feature-settings可变字体光学尺寸;OpenType 特性开关(优先用 font-variant-* 高层属性)
line-height行高;需继承时一定用无单位写法
text-align文本对齐
vertical-align垂直对齐(仅对行内级元素和表格单元格有效,对块级无效)
text-decoration文本装饰;不能被子元素取消,可配合 text-underline-offset 微调
text-transform大小写转换;只改显示,不改数据
text-shadow文字阴影(不占布局,多层时先写的在上)
white-space空白处理(pre-wrap 展示代码最常用)
overflow-wrap / word-break长单词与中文换行控制
line-clamp多行省略(配合 -webkit-line-clamp 一起写)
-webkit-text-size-adjust移动端字号自动调整;用 100% 而非 none(none 会禁止用户缩放)
@font-face / font-display自定义字体,font-display 五档取值控制字体下载期间的显示
print-color-adjust打印时是否保留颜色/背景(旧名 color-adjust,已废弃)

下章预告

下一章我们将学习颜色与背景属性,让你的网页更加丰富多彩!

最后修改 September 19, 2026: 更新 (3489033b1)