用户把系统字号调到 150%,你的页面纹丝不动——今天 Chrome 让 CSS 直接拿到这个缩放值
用户把手机系统的字号调到 150%,打开你的页面一看——字号一点没变,反而先被浏览器自带的文字自动放大算法搞成一段大一段小。这件事在移动端存在了很多年,今天终于有了标准的解法:CSS 环境变量 env(preferred-text-scale) 和 <meta name="text-scale">。
先给结论。以前你想让页面跟着用户的系统字号走,只有三条路:写死像素(完全无视用户偏好)、用 rem(在移动端根本不管用,因为浏览器不会改根字号)、或者开 text-size-adjust: auto 让浏览器的启发式算法自己猜(结果就是同一段文字大小不一)。现在第四条路出现了——浏览器或操作系统把用户的字号缩放倍数直接交给你,你决定怎么用。
为什么 rem 一直是空头支票
几乎所有教程都告诉你:用 rem 就能尊重用户的字号设置。这句话在桌面端成立,在移动端基本是假的。
W3C 的 css-env 小组在 2024 年就记录过这个不一致:Android 上的 Chrome 完全无视系统字号设置,iOS 上的 Safari 也一样;只有 Firefox 真正会调整初始字号。也就是说,BBC 团队发现 iOS App 内嵌 WebView 打开网页时,用户在系统设置里把字号调大,页面纹丝不动。而 PWA 更惨——没有浏览器 UI,用户连手动改默认字号的地方都找不到。
唯一能补救的 text-size-adjust: auto,走的又是启发式算法:它按段落长度、容器宽度猜哪些文字该放大,放大得不均匀。规范文档里直接引用了 reddit 的例子——用户选了 2 倍系统字号,text-size-adjust: auto 把评论区字号放得大小不一,有的评论小到根本读不了。
新方案怎么用
第一步,拿到用户的缩放倍数:
:root {
--text-scale: env(preferred-text-scale, 1);
}
用户设置了 1.5 倍系统字号,这个值就是 1.5;没设置就是 1——这也是为什么必须给 fallback,老浏览器不认识这个变量,不给 fallback 会整条声明失效。
第二步,两种落地姿势,看你想不想让整页跟着走。
想整页自动跟随,把根字号的缩放接上:
:root {
font-size: calc(100% * env(preferred-text-scale, 1));
}
或者更彻底,直接开 meta 标签:
<meta name="text-scale" content="scale">
这个标签的作用是把用户偏好直接烤进根元素的初始字号(也就是 font-size: medium)。它的好处是——只要你平时用的是 rem/em,代码一个字都不用改,整页就跟着用户的偏好缩放了。规范里还提到一个隐藏福利:medium 的缩放会同样作用于其它字号关键字,且是非线性缩放,小字放大比例更足、标题不会被撑爆。
第三个姿势是选择性缩放。比如你只想让正文和评论跟随用户偏好,导航和按钮保持原样:
.article-body {
text-size-adjust: calc(100% * env(preferred-text-scale, 1));
}
关键细节:env() 不只出现在属性值里,它还能用在媒体查询和容器查询里,也就是说断点可以跟着用户的字号一起移动。
.sidebar-layout {
display: grid;
@media (width > calc(50rem * env(preferred-text-scale, 1))) {
grid-template-columns: 1fr 18rem;
}
}
这条特别值得说。媒体查询里的 rem 有历史包袱——按规范,媒体查询里的相对长度永远基于初始值,你写 :root { font-size: 20px },媒体查询里的 10rem 还是算 160px 不是 200px。所以靠改根字号来让断点跟随用户偏好是不行的,必须显式乘上 env(preferred-text-scale)。容器查询里的 rem 反倒正常,因为容器查询基于的是当前元素的实际字号。
别踩这个坑:缩放会被重复应用
规范里专门警告过一句话:默认情况下文字缩放是自动生效的。如果你既让 text-size-adjust: auto 生效,又用 env(preferred-text-scale) 去乘字号,缩放就叠加了一次,文字会大得不正常。正确做法是二选一:要么 text-size-adjust: calc(100% * env(preferred-text-scale)) 交给整页,要么 text-size-adjust: none 之后再手动用这个变量缩放需要跟随的部分。
还有一个现实约束:这个特性目前只在 Chromium 系落地。Chrome、Edge、Chrome Android 和 Android WebView 从 146 版本开始支持,Opera Android 从 97 开始;Firefox 和 Safari 都还没实现,MDN 把它标成了实验性。所以现在能上线用,但必须做渐进增强——fallback 值 1 就是你的兜底,老浏览器拿到它等于没改样式。
测试方式也说一下:桌面 Chrome 可以带上 --blink-settings=accessibilityFontScaleFactor=2.0 启动,模拟 2 倍的系统字号,这时变量就会返回 2。打开 textAutosizingEnabled=true 还能顺带在桌面端生效 text-size-adjust。
今天能做的三件事
- 全文搜一遍你的 CSS,把写死的
px字号列出来,评估哪些该换成rem——这是这件事的前置条件,rem没铺开,这个变量帮不了你。 - 给根字号接上
font-size: calc(100% * env(preferred-text-scale, 1)),或者给<html>加<meta name="text-scale" content="scale">,先用一个次要页面灰度验证。 - 打开系统字号到 200%,跑一遍移动端布局,看看横向有没有撑出滚动条——WCAG 2.2 要求 200% 放大不出现横向滚动,而这个特性上线后,用户可能直接开到 300%。
移动端的字号适配,从”猜浏览器会不会帮我放大”变成了”我明确知道用户想要多大”。
评论区
登录后可评论。