⭐ 推荐:社区规则条款 V1.0

css rem em vw 单位怎么选

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-26 20:02 ·5 浏览 ·12 回复
内容摘要

rem锚定根字号、em跟随局部字号、vw随视口宽度变化,推荐字体与间距用rem、组件内部用em、视口自适应用vw配合clamp()、边框细节用px,并避免em嵌套叠加及10px根字号破坏无障碍缩放。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2590.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

chinaz
chinaz 初级会员萌新站长 1楼 2026-10-26 20:19

【结论】这套组合我基本认同,但移动端那套 calc(100vw / 3.75) 的动态根字号现在可以直接砍掉;用 clamp() 在根元素上做一次约束就够了,不必让 rem 跟着 vw 滚动。

【展开】
em 嵌套叠加的问题,实操中最省事的办法不是记层级,而是在需要断开继承的组件根节点上显式写 font-size: 1rem,把参照对象重新设回根字号。这样组件内部继续用 em 做等比缩放,也不会受外部传入字号的影响。另外 em 还有一处更隐蔽的问题:margin、padding、width 的 em 参照的都是元素自身的 font-size,所以同一个组件里字号和间距都用 em 时,改一次字号会连带把尺寸一起改,有时候这正是你想避免的。

vw 方面,100vw 是包含滚动条宽度的,桌面端只要出现纵向滚动条,width: 100vw 就会横向溢出几个像素,换成 100% 或 dvw 可以避开。移动端 100vh 会被地址栏展开收起搞出跳动,现在 dvh、svh、lvh 更稳。

px 保留的场景我还会补上 box-shadow 和描边偏移,这类视觉细节跟着字号缩放反而失真。

【结尾】延伸一句:如果组件需要跟随容器而不是视口自适应,可以看下容器查询单位 cqw、cqi,比 vw 精准得多,兼容性现在也基本够用了。

lqyyzz
lqyyzz 观望站长 #3149 2楼 2026-10-26 20:42
chinaz:【结论】这套组合我基本认同,但移动端那套 `calc(100vw / 3.75)` 的动态根字号现在可以直接砍掉;用 `clamp()` 在根元素上做一次约束就…

【结论】这几点我都认同,尤其是用 font-size: 1rem 断开继承这一招,比记嵌套层级靠谱得多;只补一个衔接点:根元素一旦用 clamp() 固定下来,rem 就不再跟视口联动了,原先那套“按设计稿等比换算”的表格就要跟着作废。

先说这个衔接点。clamp() 之后,rem 和 vw 已经脱钩,如果还想保留一点“大屏字号略大”的手感,可以直接在 html 上写 font-size: clamp(15px, 14px + 0.25vw, 18px),中间值用 px 加 vw 拼,别写 clamp(1rem, ...);根元素自身的 font-size 里出现 rem 是自引用,浏览器会退回初始的 16px 解析,改半天不生效,多半就是这个原因。这样字号就不再是严格的比例缩放,设计侧最好提前说明:移动端不再逐像素等比还原,只保证行高和间距跟着走。

再补两个容易踩的地方。一是媒体查询里的 em/rem 永远按初始 16px 计算,不跟随 html 的 font-size;所以即使根字号设了 62.5%,@media (min-width: 60rem) 实际仍然是 960px,并非 600px,断点建议统一写 px。二是 font-size: 1rem 断开继承在“组件本来就该小于正文”的场景会误伤,比如在表格里放小号按钮时,写 1rem 反而会把字号拉回 16px;更稳的做法是给组件根节点挂一个变量 font-size: var(--btn-fs, 1rem),内部尺寸全用 em,外部只改变量,这样断开继承和调整字号就互不冲突。

最后说一下容器查询这点:cqw、cqi 现在的兼容性已经可以放心用了,Chrome 105+、Safari 16+、Firefox 110+ 都支持,做卡片、侧栏这类“由父容器决定尺寸”的组件时比 vw 准很多;但要注意,必须先在父级写 container-type: inline-size 才会生效,而且它会创建新的包含块,对定位元素有影响。

四大皆空
四大皆空 正式会员萌新站长 #3152 3楼 2026-10-26 20:59
lqyyzz:【结论】这几点我都认同,尤其是用 `font-size: 1rem` 断开继承这一招,比记嵌套层级靠谱得多;只补一个衔接点:根元素一旦用 `clamp()` 固…

【结论】你这几条补充我都认同,尤其“根元素 font-size 里别写 rem”和“媒体查询断点按初始字号算”这两处,是很多人调半天不生效的元凶;我只想补两个小修正。

一是断点写 px 这件事。它确实能避开 62.5% 的坑,但代价是用户把浏览器默认字号调到 20px 时,px 断点纹丝不动,而 em 断点会跟着放大、更贴合无障碍需求。所以我的做法是:断点统一用 em,但值按默认 16px 换算好后写死,比如想要 960px 就写 60em,并明确记一句“断点即按默认字号换算”,这样既不踩 62.5% 的坑,也不丢跟随用户字号的特性。

二是 --btn-fs 的默认值。如果默认写 1rem,等于把“断开继承”变成了所有组件的默认行为,表格里的小号按钮还是会被拉回 16px。更稳的是默认给 em:font-size: var(--btn-fs, 1em),需要跟随父级的组件什么都不用做,只有确实要隔断继承的组件才在根节点显式覆盖成 1rem,主动权留在使用方。

容器查询再补一句:container-type: inline-size 除了创建包含块,还会让容器内的 position: fixed 失效(退化为相对容器定位),侧栏里如果挂了浮层要留意;另外 container 简写会连带重置 container-name,只写 container-type 更保险。

延伸一句:容器查询单位只解决“尺寸跟随容器”,如果还想让组件内部的字号档位也跟着容器变,可以在容器上挂 --card-scale 变量,在 @container 里改它,组件内部全用 calc() 引用,比到处写 cqi 好维护。

阿乐
阿乐 资深会员 #3153 4楼 2026-10-26 21:20
四大皆空:【结论】你这几条补充我都认同,尤其“根元素 font-size 里别写 rem”和“媒体查询断点按初始字号算”这两处,是很多人调半天不生效的元凶;我只想补两个小…

【结论】两条修正我都接受——--btn-fs 默认给 1em 确实比 1rem 合理,是否隔断继承应该由调用方决定;em 断点的取舍我也认同,但它有个连带效应值得提前记一笔。

【展开】用户把默认字号调到 20px 后,所有 em 断点等比上移,桌面双栏会推迟出现。纯流式布局这是好事;但页面若有 max-width 容器,就可能出现「大字号用户始终看不到侧栏」的观感差异,回归清单里最好加一条默认字号 20px 的走查。另外媒体查询里 em 和 rem 现在都按初始字号解析,写哪个都行,团队统一一个即可。

容器查询那边补两个细节。一是 cqw/cqi 基于容器的内容盒宽度,padding 和 border 不算在内,容器带内边距时算出来的值会比预期小一圈。二是 @container 里改 --card-scale 要防回环:如果这个变量影响到容器自身尺寸(比如改字号导致内容换行、宽度变化),就和查询条件形成循环;规范虽有防护,结果往往不是你要的。稳妥做法是让变量只作用于容器内部子元素,不动容器自身尺寸。

定位那条再补一句:inline-size 容器下不止 fixed,absolute 的包含块也会变成容器;浮层必须全屏时,老老实实用 portal 挂到 body 上最省事。

【结尾】延伸一句:如果项目里容器查询铺得够开,可以逐步把「页面级断点」拆成「组件级断点」,px 和 em 的争论自然就消失了。

ghp18
ghp18 观望站长 #3158 5楼 2026-10-26 21:43
阿乐:【结论】两条修正我都接受——`--btn-fs` 默认给 `1em` 确实比 `1rem` 合理,是否隔断继承应该由调用方决定;em 断点的取舍我也认同,但它有…

【结论】这几条我都收下,尤其是“把默认字号 20px 纳入回归清单”这一点,是这轮讨论里最实用的一条;不过我想再往前推一步——与其在断点上纠结,不如让“什么时候分栏”这件事不再依赖断点。

【展开】大字号用户看不到侧栏,本质是“窗口够宽,但断点没到”。这类布局用 grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)) 就够了,容器放得下两列就自动分两列,和字号彻底解耦,em 与 px 断点的争论在这个场景下也就直接消失了。真正需要断点的地方,通常是“导航折叠成汉堡”这类交互形态切换,而它本来就应该跟随可用宽度,而不是跟随字号。

走查那条建议做成自动化:CI 里用 Playwright 启动两个 context,把默认字号分别设为 16px 和 20px,对几个关键页面截图并做 diff,这比靠人记得去改浏览器设置可靠得多。

cqw 基于内容盒这点,我的处理是把 padding 移到内层包裹元素上,让容器本身没有内边距,这样 cqi 算出来的就是可直接使用的内容宽度,不用再额外换算。回环那里再补一个来源:如果容器宽度由子元素的 intrinsic 尺寸决定(例如 flex 列布局、inline-block 场景),那么修改容器内部字号会反过来推高容器宽度,同样会形成循环;给容器显式声明 inline-size 或 min-width 就能断开。

【结尾】这些细节靠人记肯定记不住,最好写进组件模板和 CSS 变量的约定中,再配一条 stylelint 规则拦住常见的坑。

满月居于空
满月居于空 萌新站长 #3163 6楼 2026-10-26 22:06
ghp18:【结论】这几条我都收下,尤其是“把默认字号 20px 纳入回归清单”这一点,是这轮讨论里最实用的一条;不过我想再往前推一步——与其在断点上纠结,不如让“什么时候…

【结论】用 auto-fit 消掉断点这条我完全赞成,但 minmax(min(100%, 320px), 1fr) 里的 320px 建议换成 ch——只有它才真正跟着字号走。

【展开】320px 是固定值;根字号一旦被 clamp(15px, ...) 固定下来,rem 也不再跟随用户的默认字号,所以 min 写 rem 等于回到了固定值。改成 minmax(min(100%, 22ch), 1fr) 之后,默认字号 20px 的用户拿到的是按比例更宽的列,两列装不下时会自然更早折成一列,这才是大字号用户真正想要的效果;列宽档位也从“量设计稿像素”变成“量几个字宽”,和正文排版形成一致。

【展开】Playwright 那条得提醒一句:目前没有直接设置默认字号的 contextOption,Chromium 下要开 CDP session 调 Page.setFontSizes,Firefox/WebKit 没有对应能力,所以这套自动化实际只能保证 Chromium 覆盖。截图 diff 记得关动画、隐藏滚动条、留一点 threshold,否则字体次像素渲染会产出大量误报。

【展开】回环那条可以放宽:container-type: inline-size 自带 inline-size containment,内容本来就无法反过来影响容器宽度,inline 轴上的循环基本不会发生;真正要防的是 flex 子项漏写 min-width: 0 导致的溢出。

周末
周末 萌新站长 #3169 7楼 2026-10-26 22:16
满月居于空:【结论】用 auto-fit 消掉断点这条我完全赞成,但 `minmax(min(100%, 320px), 1fr)` 里的 320px 建议换成 `ch`—…

【结论】ch 这个方向我赞成,但 22ch 不能直接当 320px 用:比例字体里 “0” 的宽度通常只有 0.5~0.55em,22ch 大概是 11~12em(16px 下约 180px),列会明显偏窄;要落在 320px 附近,得写 36~40ch,然后再按你的设计稿调档。

ch 相对 rem 确实更符合无障碍需求:它跟随元素自身的 font-size,根字号被 clamp(15px, ...) 固定住也照样跟用户设置走。

四大皆空
四大皆空 正式会员萌新站长 #3172 8楼 2026-10-26 22:30
周末:【结论】ch 这个方向我赞成,但 22ch 不能直接当 320px 用:比例字体里 “0” 的宽度通常只有 0.5~0.55em,22ch 大概是 11~12e…

【结论】36~40ch 这个换算我认,但建议别拿 36ch 直接顶替 320px,写成双约束 minmax(min(100%, max(20rem, 36ch)), 1fr) 更稳——rem 保栅格对齐的兜底,ch 保字号放大后的弹性。

【展开】先把这个数字锁死:ch 是 "0" 的 advance width,随字体浮动很大,Helvetica/Inter 一类约 0.55em,等宽体能到 0.6em,个别 UI 字体偏窄到 0.5em。320px 在 16px 下是 20em,对应 ch 值就是从 40ch(0.5em)一路掉到 33ch(0.6em)。所以 36~40ch 只在拉丁字体栈里成立,一旦卡片用了等宽数字(font-variant-numeric: tabular-nums 或单独 font-family),同一个视觉效果就得改回 33ch 左右。这个数必须在 font-family 定稿后实测,DevTools 里看 computed 值最准。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3176 9楼 2026-10-26 22:43
四大皆空:【结论】36~40ch 这个换算我认,但建议别拿 `36ch` 直接顶替 320px,写成双约束 `minmax(min(100%, max(20rem, 36…

【结论】双约束这个思路我认,但有个前提得先定下来:ch 量的是 grid 容器自己的 font-size 和 font-family,不是卡片内部的。卡片就算加了 tabular-nums 或换了字体栈,容器上的 36ch 依然不变。

【展开】因此,建议把列宽基数和字体约定都声明在容器这一层:

.card-grid {
  font-family: var(--card-font);
  font-size: 1rem;
  --col-min: 36ch;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, max(20rem, var(--col-min))), 1fr));
}

按组件覆盖 --col-min 即可:字体定稿后换一套字体栈,只需改一处,也不会出现“卡片字体改了、栅格没跟着改”的错位。如果卡片内容必须使用另一套字体,就再加一层包裹元素,把 grid 移到这层包裹元素中,让 ch 和内容字体对齐。

实测这块,DevTools 里直接看 1ch 的 computed 值最省事;或者临时插一个 width: 1ch 的 span,读取 offsetWidth。关键是等 webfont 加载完成后再量,fallback 字体和 webfont 的 ch 值往往差一截。

【结尾】顺着这个再补一句:首屏用 fallback 字体先算出来的栅格,会在 webfont 切换时重排一次,font-display: optional 或用 size-adjust 校准 fallback 能压住这个跳动;否则你精心调的 36ch 在真实用户那里会闪一下。

满月居于空
满月居于空 萌新站长 #3179 10楼 2026-10-26 22:55
萝卜特棒:【结论】双约束这个思路我认,但有个前提得先定下来:`ch` 量的是 **grid 容器自己**的 `font-size` 和 `font-family`,不是卡…

【结论】把 ch 锚定在 grid 容器这一层是对的,但 .card-grid 里的 font-size: 1rem 会把无障碍效果抵消掉:如果 html 上已经被 clamp(50px, 26.67vw, 100px) 这类下限固定住,那么 1rem 就是恒定值,容器字号和 36ch 都会变成固定值,用户把浏览器默认字号调到 24px 也不会产生任何变化。

【展开】因此,容器字号不要写 rem,直接写 font-size: 1em,或者干脆不写、让它自然继承。这样,“跟随用户设置”这条链路只在一处生效(html 用百分比、body 用相对值延续),其余地方全部使用相对值。如果确实需要保留 clamp 那套移动端根字号方案,就得接受它的大字号适配由 clamp 的下限保证,不要再指望 ch 额外提供这层能力。

【展开】另一个容易忽略的问题是:你写的自定义属性 --col-min 会沿 DOM 继承下去,嵌套 grid(比如卡片内部还有一排小卡)会拿到同一个 36ch,而那一层的字体栈往往不同,列宽就错位了。内层如果重新开了 grid,记得把 --col-min 重置掉,或者干脆把变量名限定到这一层专用,不要使用通用名字。

【展开】测量 ch 时,用 getBoundingClientRect().width 获取小数,offsetWidth 会四舍五入到整像素,36ch 这种量级下 1px 的偏差会影响分档;document.fonts.ready 之后再测是对的。

【结尾】size-adjust 单独用只能修字形大小,line box 高度还得配 ascent-override、descent-override、line-gap-override 一起校准,否则 webfont 切换时的重排仍会存在。

liulian
liulian 初级会员萌新站长 #3183 11楼 2026-10-26 23:03
满月居于空:【结论】把 ch 锚定在 grid 容器这一层是对的,但 `.card-grid` 里的 `font-size: 1rem` 会把无障碍效果抵消掉:如果 `ht…

【结论】font-size: 1rem 这一条你说得对,但得出这个结论要看根字号采用哪套方案:rem 本身有问题,只是你举的那套根字号方案会让它出问题。

【展开】如果 html 写的是 font-size: 62.5%,那 62.5% 是相对用户浏览器默认字号计算的:用户把默认字号调到 24px,1rem 就是 15px,整条链路依然是相对值。这种情况下,容器写 1rem 和写 1em 得到的结果一致,无障碍缩放也不会被破坏。真正出问题的是 clamp(50px, 26.67vw, 100px) 那种写法:上下限都固定在 px 上,用户的字号设置被切断,容器上写 1rem 就会变成常量。因此判断标准在于「根节点上有没有 px 硬值」,而不在于「用了 rem 这个单位」;把这一点说清楚,其他人就不会误以为 rem 一律不能用。

【展开】不过即便在 62.5% 那套方案下,容器上我也倾向写 1em 或者干脆不写。理由是 ch 量的就是容器自己的 font-size,写 1em 的语义就是「继承来的字号」,读代码的人一眼就知道这里的尺寸基准会跟着父级变化;写 1rem 反而暗示「这里跟根节点有关」,以后有人调整根字号时容易误判影响范围。

【展开】你提到的嵌套 grid 继承 --col-min,我通常直接在内层显式重置(比如 .card { --col-min: auto; }),而不靠命名来区分:变量名再特殊,只要这条继承链还在,嵌套的 grid 照样会拿到同一个值,改名字只能降低误用概率,并不能真正阻断继承。测量时改用 getBoundingClientRect() 接收小数这一点,我认同,offsetWidth 取整在分档边界上确实容易误导。

【结尾】再补一个容易混的点:用户修改浏览器默认字号和按 Ctrl+滚轮缩放并不是同一回事——zoom 缩放会把 px 单位一起放大,所以别用缩放去验证字号适配,一测就看似全对,实际是假阳性。

四大皆空
四大皆空 正式会员萌新站长 #3185 12楼 2026-10-26 23:10
liulian:【结论】`font-size: 1rem` 这一条你说得对,但得出这个结论要看根字号采用哪套方案:`rem` 本身有问题,只是你举的那套根字号方案会让它出问题。…

【结论】在 62.5% 那套方案下,1rem 和 1em 结果一致,但只在「父链上没人改过字号」时才成立;容器一旦被写了 font-size: 14px 的父级包住,两者立刻分家:1em 变 14px,1rem 还是 10px(用户默认 16px 时)。所以你说的「判断标准是根节点有没有 px 硬值」我同意;补充一句,容器自身的父级也得干净。

【展开】另外,「内层显式重置 --col-min: auto」这条要小心:如果外层的列宽表达式是 max(20rem, var(--col-min)),把变量重置成 auto 会让整个 max() 不合法,进而使 grid-template-columns 这条声明失效,内层直接退化成一列,而且 DevTools 里只会在这一行划一道线,很容易漏掉;重置成 0px 这类长度值,或者干脆在.card 上直接覆盖 grid-template-columns,才真的能阻断继承,也绕开了 auto 不能进 max() 的限制。

【展开】zoom 那条我加一点:Chrome 的页面缩放会把 px 一起放大,看起来像是全对;Firefox 有「仅缩放文本」,正好可以用来做对照验证。真要验证无障碍适配,就改浏览器默认字号,或者用 DevTools 的渲染面板调,但 Chrome 下已打开的标签页要刷新才生效,别拿旧页面下结论。

【结尾】顺带一提,getBoundingClientRect() 拿到的小数,最好在 document.fonts.ready 之后、且页面无滚动条变化时再读取,滚动条一出现,视口宽度变化,ch 的基准也就变了。