前端小白别再搞混了:rem和em到底咋用才不翻车(附实战避坑指南)
- 前端小白别再搞混了:rem和em到底咋用才不翻车(附实战避坑指南)
-
- 为啥有人死磕px不用?为啥大厂项目清一色rem?
- em其实是个"墙头草",看谁脸色吃饭
- rem才是真正的"独立人设",只认html的字号
- 别再一股脑全换成rem!有些地方em更香
- 响应式布局里,rem怎么配合媒体查询玩出花
- 移动端适配别只靠rem,viewport也得安排上
- 字体大小用em?小心嵌套爆炸!
- 组件化开发时,rem和em怎么选才不互相背刺
- 设计师给的是px,前端怎么优雅转成rem-em
- 浏览器默认字号不一样?别慌,一行CSS搞定
- 你以为1rem=16px?某些安卓机可不答应
- em在按钮、图标、行高中那些骚操作
- 用CSS变量+rem,让主题切换丝滑到飞起
- 遇到单位混乱的祖传代码,怎么快速定位问题
- 调试技巧:Chrome DevTools里怎么看实际计算值
- 别信网上那些"万能公式",适配还得看场景
- 真·实战:用rem搭个移动端首页,em处理内部间距
- 老项目想切rem?小心这些隐藏雷区
- 为什么有些团队禁用em?背后有苦衷
- 最后唠一句:单位只是工具,别被它绑架了思路
前端小白别再搞混了:rem和em到底咋用才不翻车(附实战避坑指南)
刚入门前端那会儿,看到别人写CSS动不动就用rem、em,还以为是某种神秘咒语。结果自己一上手,页面不是缩成蚂蚁就是撑爆屏幕,差点以为显示器坏了。今天咱就掰开揉碎聊聊这两个"亲戚单位"——它们长得像,脾气可差远了。
为啥有人死磕px不用?为啥大厂项目清一色rem?
说实话,我刚学CSS的时候,px用着多爽啊,写多少就是多少,浏览器说一不二。但很快就被现实打脸了。
px的硬伤在于它太"死"了。你写个font-size: 16px,用户把浏览器字号调到最大,你的文字还是16px,纹丝不动。这在可访问性(a11y)眼里就是原罪。而且做响应式的时候,你要写一堆媒体查询去改各种px值,维护起来想死。
大厂爱用rem,核心就俩字:可控。改一个地方(html的font-size),整个页面的相对单位全跟着变。做移动端适配的时候,配合js动态计算根字号,一套代码适配N种屏幕,它不香吗?
但px也不是一无是处。边框宽度用px就很稳,border: 1px solid #ccc,你换成rem试试?在某些高清屏上可能会糊成0.5px或者粗成2px,看着膈应。
em其实是个"墙头草",看谁脸色吃饭
em这货特别有意思,它的计算基准是父元素的字号。注意是"字号",不是"宽度"不是"高度",就是font-size。
看个例子就懂:
/* 父元素 */
.parent {
font-size: 20px;
}
/* 子元素 */
.child {
font-size: 1.5em; /* 20px * 1.5 = 30px */
padding: 2em; /* 注意!这里按谁的算? */
margin-bottom: 1em; /* 又是按谁的算? */
}
这里有个大坑!很多人以为em在所有属性上都按父元素的font-size算。错!em在font-size属性上,参照的是父元素的font-size;但在其他属性(如padding、margin、width)上,参照的是元素自身的font-size。
所以上面代码里:
- .child的font-size是30px(继承父的20px再乘1.5)
- padding是60px(30px * 2)
- margin-bottom是30px(30px * 1)
看出门道了吗?em在font-size上"套娃"了一次,其他属性又基于这个新的font-size算。这就是为什么em被称为"墙头草"——它看的是当前上下文的字号。
再举个更刺激的嵌套例子:
.grandpa {
font-size: 16px;
}
.dad {
font-size: 1.5em; /* 16 * 1.5 = 24px */
}
.son {
font-size: 1.5em; /* 24 * 1.5 = 36px */
}
.grandson {
font-size: 1.5em; /* 36 * 1.5 = 54px */
}
三层嵌套下来,字号从16px爆炸到54px。这就是em的"复利效应",也是为什么很多人谈em色变。但别急着拉黑它,后面会说它发光发热的场景。
rem才是真正的"独立人设",只认html的字号
rem(root em)就简单多了,它永远只认根元素(html)的font-size。不管你在哪个层级,不管父元素怎么折腾,1rem就是html字号的倍数。
html {
font-size: 16px;
}
.anywhere {
font-size: 1.5rem; /* 永远是24px,除非改html的font-size */
padding: 2rem; /* 永远是32px */
width: 10rem; /* 永远是160px */
}
.deep .nested .element {
font-size: 1rem; /* 还是16px,不受中间层影响 */
}
这种"一根筋"的特性让rem特别适合做全局尺度系统。你想啊,设计规范里定义了"小、中、大、超大"几个字号层级,用rem定义好后,整个项目引用这些变量,后期统一调整根字号,全站跟着变,设计师改规范也不怕。
别再一股脑全换成rem!有些地方em更香
这话可能跟网上很多教程唱反调,但我得说:em在特定场景下比rem好用得多。
场景一:组件内部的相对比例
比如一个按钮组件,你想让padding跟文字大小成比例。用em的话,不管按钮是大号还是小号,只要改font-size,padding自动跟着缩放,比例永远协调:
.btn {
/* 基础样式 */
display: inline-flex;
align-items: center;
justify-content: center;
/* 用em实现"自适应内边距" */
padding: 0.75em 1.5em; /* 垂直0.75倍字号,水平1.5倍字号 */
font-size: 1rem; /* 默认16px */
border-radius: 0.375em; /* 圆角也跟字号走 */
}
.btn-large {
font-size: 1.25rem; /* 改成20px */
/* padding自动变成15px 30px,比例保持0.75:1.5 */
}
.btn-small {
font-size: 0.875rem; /* 改成14px */
/* padding自动变成10.5px 21px */
}
看到没?用em做组件内部的比例关系,改一个font-size,整个组件的尺度系统全联动。你用rem写padding试试?得同时改font-size和padding两个值,还容易算错比例。
场景二:行高(line-height)
无单位的line-height(如1.5)其实 behaves 很像em,但更简单。不过如果你想显式控制,em也很香:
.text-content {
font-size: 1rem;
line-height: 1.6em; /* 1.6倍当前字号,约25.6px */
}
.lead-text {
font-size: 1.25rem;
line-height: 1.5em; /* 自动变成30px,比例保持 */
}
场景三:图标和文字的对齐
图标字体(如Font Awesome)或者SVG图标,想让图标大小跟文字走:
.icon-text {
font-size: 1rem;
}
.icon-text::before {
content: '';
display: inline-block;
width: 1em; /* 跟文字一样宽 */
height: 1em; /* 跟文字一样高 */
background: url('icon.svg') center/contain no-repeat;
vertical-align: middle;
margin-right: 0.5em; /* 间距也是相对字号 */
}
这样图标永远跟文字等比例,换字号不用改图标样式。
响应式布局里,rem怎么配合媒体查询玩出花
最经典的玩法是阶梯式根字号:
/* 移动端基准 */
html {
font-size: 14px;
}
/* 平板 */
@media (min-width: 768px) {
html {
font-size: 15px;
}
}
/* 小桌面 */
@media (min-width: 1024px) {
html {
font-size: 16px;
}
}
/* 大桌面 */
@media (min-width: 1440px) {
html {
font-size: 18px;
}
}
配合rem写的组件,整个页面会"阶梯式放大",比单纯改布局更细腻。
但还有更骚的操作——流体排版(Fluid Typography):
/* 用clamp实现根字号的平滑过渡 */
html {
font-size: clamp(14px, 0.5vw + 12px, 18px);
}
/* 或者用更精细的calc */
html {
font-size: calc(14px + (18 – 14) * ((100vw – 375px) / (1440 – 375)));
}
这样字号在375px到1440px之间平滑变化,不是阶梯跳变。所有用rem定义的尺寸都会跟着流体缩放,响应式体验更丝滑。
移动端适配别只靠rem,viewport也得安排上
很多人以为用了rem就万事大吉,结果在iPhone和安卓上表现不一致。viewport设置不对,rem算出来也是错的。
标准viewport配置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
但user-scalable=no现在不推荐,影响可访问性。更友好的写法:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
配合JS动态计算rem(这是大厂标配方案):
// 假设设计稿是750px宽
(function() {
const docEl = document.documentElement;
const dpr = window.devicePixelRatio || 1;
// 设置data-dpr属性,方便CSS做高清屏适配
docEl.setAttribute('data-dpr', dpr);
function setRootFontSize() {
const width = docEl.clientWidth;
// 750px设计稿,1rem = 100px(方便计算)
// 即:width / 7.5
const fontSize = width / 7.5;
docEl.style.fontSize = fontSize + 'px';
}
setRootFontSize();
window.addEventListener('resize', setRootFontSize);
// 处理安卓字体大小被系统设置影响的问题
if (typeof WeixinJSBridge === "object" && typeof WeixinJSBridge.invoke === "function") {
// 微信环境特殊处理
WeixinJSBridge.invoke('setFontSizeCallback', { fontSize: 0 });
}
})();
这样1rem等于100px(在750px宽设备上),设计师给的100px直接写1rem,50px写0.5rem,心算就行。
但要注意:某些安卓机的WebView会强制修改根字号,导致你的rem计算全乱。上面代码里的微信特殊处理就是干这个的。更通用的保险方案:
/* 用!important强制覆盖系统干预 */
html {
font-size: 16px !important;
}
/* 或者更精细地,针对特定属性保护 */
@media screen {
html {
/* 防止被用户代理样式表覆盖 */
font-size: 16px;
}
}
字体大小用em?小心嵌套爆炸!
前面说了em的嵌套复利问题,这里再给个血淋淋的例子:
<div class="article">
<p>这是一段文字
<span class="highlight">里面有个高亮
<strong>再嵌套个加粗</strong>
</span>
</p>
</div>
.article {
font-size: 16px;
}
.article p {
font-size: 1.2em; /* 19.2px */
}
.article .highlight {
font-size: 0.9em; /* 19.2 * 0.9 = 17.28px,比父级还小! */
}
.article strong {
font-size: 1.1em; /* 17.28 * 1.1 = 19.008px */
}
看出诡异之处了吗?.highlight想稍微小一点,写了0.9em,结果算出来比.article的16px还大(17.28px)。这就是em的"相对性陷阱"——你以为是相对于最初基准,实际上是相对于直接父级。
解决方案:用rem做字体大小,em做内部比例
.article {
font-size: 1rem; /* 16px */
}
.article p {
font-size: 1.125rem; /* 18px,绝对值,不嵌套 */
}
.article .highlight {
font-size: 0.875rem; /* 14px,绝对值,确实比16px小 */
padding: 0.5em 1em; /* 但内部间距用em,相对于14px */
border-radius: 0.25em;
}
这样字体大小不会套娃,但组件内部的比例关系依然保持。
组件化开发时,rem和em怎么选才不互相背刺
现代前端都是组件化开发,每个组件可能是不同人写的,嵌套层级未知。这时候单位选择策略很重要。
策略一:组件边界用rem,组件内部用em
/* Card组件 */
.card {
/* 外部尺寸用rem,确保在不同上下文里表现一致 */
width: 20rem;
margin-bottom: 1.5rem;
padding: 1.5rem; /* 内部间距也可以用rem,看设计规范 */
/* 但内部元素的比例关系用em */
font-size: 1rem;
}
.card-title {
font-size: 1.25rem; /* 标题大小绝对值 */
margin-bottom: 0.5em; /* 但下边距是标题字号的0.5倍 */
line-height: 1.4em;
}
.card-body {
font-size: 0.875rem;
line-height: 1.6em;
}
这样把Card放进任何父级,它的"外壳"尺寸稳定,但内部排版比例协调。
策略二:CSS变量(自定义属性)+ 单位混合
更现代的做法是用CSS变量做设计令牌:
:root {
/* 尺寸令牌,用rem */
–space-xs: 0.25rem;
–space-sm: 0.5rem;
–space-md: 1rem;
–space-lg: 1.5rem;
–space-xl: 2rem;
/* 字体令牌,用rem */
–text-xs: 0.75rem;
–text-sm: 0.875rem;
–text-base: 1rem;
–text-lg: 1.125rem;
–text-xl: 1.25rem;
/* 比例因子,无单位或em */
–ratio-tight: 1.25;
–ratio-normal: 1.5;
–ratio-loose: 1.75;
}
.component {
/* 用rem确保跨组件一致 */
padding: var(–space-md);
font-size: var(–text-base);
/* 内部比例用em或变量 */
line-height: var(–ratio-normal); /* 无单位,相对于自身字号 */
}
.component-header {
font-size: var(–text-lg);
margin-bottom: calc(var(–space-sm) * var(–ratio-tight)); /* 混合计算 */
}
设计师给的是px,前端怎么优雅转成rem-em
这是日常撕逼点。设计师在Figma里画了个按钮,高40px,内边距左右24px,字号16px。你给写成rem,下次设计师改稿到44px,你还得重新算。
方案一:Sass/Less函数自动转
// 基准字号16px
$root-font-size: 16px;
// px转rem函数
@function rem($px) {
@return $px / $root-font-size * 1rem;
}
// px转em函数(相对于当前上下文)
@function em($px, $context: $root-font-size) {
@return $px / $context * 1em;
}
// 使用
.button {
height: rem(40px); // 2.5rem
padding: 0 rem(24px); // 0 1.5rem
font-size: rem(16px); // 1rem
// 内部比例用em,基于自身字号(16px->1rem)
line-height: em(24px, 16px); // 1.5em
}
方案二:PostCSS插件自动处理
更省事的是用postcss-pxtorem插件,配置好基准,写px自动转rem:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-pxtorem')({
rootValue: 16, // 基准字号
propList: ['*'], // 所有属性都转换
selectorBlackList: ['.no-rem'], // 排除特定选择器
minPixelValue: 2 // 小于2px不转(防止边框消失)
})
]
}
然后你直接写px:
.button {
height: 40px; /* 编译后变成2.5rem */
padding: 0 24px; /* 编译后变成0 1.5rem */
}
但注意:这个方案会把所有px都转,包括你不想转的(比如1px边框)。配合selectorBlackList或精确控制属性列表很重要。
方案三:VS Code插件实时看转换
装个"px to rem"插件,选中px值,Alt+Z一键转rem,心里更有底。
浏览器默认字号不一样?别慌,一行CSS搞定
不同浏览器、不同系统的默认字号可能不一样。Windows通常是16px,但Mac可能默认更大,用户还可能自定义。
保险做法:
/* 重置为统一基准,但尊重用户偏好 */
html {
font-size: 100%; /* 继承浏览器默认,通常是16px */
}
/* 如果检测到用户改了默认字号(比如改成20px),用rem的元素会跟着大,但px的不会 */
更精细的检测用户偏好:
/* 检测用户是否偏好大字号 */
@media (prefers-reduced-motion: no-preference) {
/* 动画相关 */
}
/* 检测对比度偏好 */
@media (prefers-contrast: high) {
/* 高对比度模式 */
}
但最实用的还是用rem做主要布局单位,这样用户调浏览器字号时,你的页面会响应式放大,而不是固定死。
你以为1rem=16px?某些安卓机可不答应
前面提过,某些国产安卓机的WebView会强制缩放字体,特别是微信、QQ内置浏览器。这会导致:
- 你设了html{font-size: 16px},但浏览器强制改成18px
- 所有rem计算都偏大,布局错乱
检测和修复方案:
// 检测实际根字号是否被篡改
(function() {
const docEl = document.documentElement;
const computedFontSize = window.getComputedStyle(docEl).fontSize;
const expectedFontSize = '16px';
if (computedFontSize !== expectedFontSize) {
console.warn('根字号被篡改!实际值:', computedFontSize);
// 方案1:强制重置(可能无效,因为浏览器优先级更高)
docEl.style.fontSize = expectedFontSize;
// 方案2:计算比例,调整所有rem基准(复杂,但有效)
const actualPx = parseFloat(computedFontSize);
const ratio = 16 / actualPx;
docEl.style.fontSize = (16 * ratio) + 'px'; // 绕一圈回到16px的效果
}
})();
更简单的预防方案是用viewport的target-densitydpi(已废弃)或者干脆用flexible布局方案(阿里开源的lib-flexible),它处理了大部分安卓奇葩情况。
em在按钮、图标、行高中那些骚操作
说几个em特别香的实战技巧。
技巧一:完美圆形按钮
想让按钮宽高相等,且跟文字大小关联:
.icon-btn {
font-size: 1rem;
width: 2.5em; /* 40px,基于字号 */
height: 2.5em; /* 40px,完美圆形 */
padding: 0; /* 去掉默认内边距 */
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
}
.icon-btn-large {
font-size: 1.5rem; /* 改成24px */
/* 宽高自动变成60px,比例保持 */
}
技巧二:自适应行高
设计师说要"1.5倍行距",用em最直接:
.paragraph {
font-size: 1rem;
line-height: 1.5em; /* 24px */
/* 如果父级改了字号 */
font-size: 0.875rem; /* 14px,行高自动变成21px */
}
无单位的line-height: 1.5效果一样,但em更明确表达"相对于字号"的意图。
技巧三:图标系统的比例缩放
.icon-system {
/* 基础图标大小 */
–icon-size: 1em;
display: inline-block;
width: var(–icon-size);
height: var(–icon-size);
vertical-align: middle;
}
.icon-system.small {
–icon-size: 0.75em; /* 相对于父级字号的0.75倍 */
}
.icon-system.large {
–icon-size: 1.5em;
}
配合currentColor还能让图标颜色跟文字走:
.icon-system svg {
fill: currentColor; /* 继承父级color */
}
用CSS变量+rem,让主题切换丝滑到飞起
暗黑模式、主题切换现在都是标配。用CSS变量+rem,切换起来特别顺:
:root {
/* 亮色模式基准 */
–bg-color: #ffffff;
–text-color: #333333;
–border-color: #e0e0e0;
/* 尺寸用rem */
–spacing-unit: 1rem;
–radius-unit: 0.25rem;
}
@media (prefers-color-scheme: dark) {
:root {
–bg-color: #1a1a1a;
–text-color: #e0e0e0;
–border-color: #404040;
}
}
/* 手动切换主题 */
[data-theme="dark"] {
–bg-color: #1a1a1a;
–text-color: #e0e0e0;
–border-color: #404040;
}
/* 使用 */
.card {
background: var(–bg-color);
color: var(–text-color);
border: 1px solid var(–border-color);
padding: calc(var(–spacing-unit) * 1.5);
border-radius: calc(var(–radius-unit) * 2);
}
改data-theme属性,全站颜色瞬间切换,尺寸不变。如果用JS动态改根字号,还能实现"紧凑模式"和"舒适模式"的切换:
// 紧凑模式
document.documentElement.style.fontSize = '14px';
// 舒适模式
document.documentElement.style.fontSize = '18px';
所有rem定义的尺寸都会响应,比改一堆class爽多了。
遇到单位混乱的祖传代码,怎么快速定位问题
老项目往往是px、em、rem、甚至pt混用,改一处崩三处。这时候需要系统性排查。
步骤一:用Chrome DevTools看计算值
选中元素,Computed面板里能看到最终计算值。注意看:
- font-size的计算来源(继承自谁)
- width/height/padding的实际像素值
- 有没有被!important覆盖
步骤二:用CSS概览(CSS Overview)
Chrome DevTools的Experiments里开启CSS Overview,能统计全站用了哪些单位、颜色、字体。一眼看出单位混乱程度。
步骤三:渐进式替换策略
别一股脑全改,按优先级:
步骤四:用CSS变量做隔离层
/* 先定义变量,把单位逻辑集中管理 */
:root {
–legacy-base: 16px;
–space-unit: 1rem; /* 新的rem基准 */
}
/* 老代码用变量过渡 */
.old-component {
width: calc(320px / var(–legacy-base) * var(–space-unit)); /* 20rem */
}
调试技巧:Chrome DevTools里怎么看实际计算值
除了Computed面板,还有几个隐藏技巧。
技巧一:看继承链
Elements面板的Styles里,把鼠标悬停在属性值上,能看到继承来源。比如font-size继承自.parent,会显示出来。
技巧二:用控制台实时算
// 获取元素的实际样式
const el = document.querySelector('.my-element');
const styles = window.getComputedStyle(el);
console.log('实际font-size:', styles.fontSize);
console.log('实际padding:', styles.padding);
console.log('实际width:', styles.width);
// 算rem对应的px
const rootSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
const remValue = 1.5;
console.log(`${remValue}rem = ${remValue * rootSize}px`);
技巧三:设备模式测试不同DPR
DevTools的设备模式里可以切换DPR(设备像素比),测试高清屏下的rem表现。特别是用了vw或vh配合rem时,要确保计算正确。
别信网上那些"万能公式",适配还得看场景
网上流传很多"1rem=100px"的"万能公式",说什么移动端直接用,除以100就行。但:
- 设计稿宽度不一致:有的是750px,有的是375px,有的是640px,公式要变
- 是否包含缩放:有些方案用window.devicePixelRatio做了缩放,1rem实际对应的是逻辑像素还是物理像素要分清
- 平板和桌面怎么办:那些公式只考虑手机,iPad Pro上字体会大得吓人
更灵活的做法:
// 根据设计稿宽度动态计算
const designWidth = 750; // 设计稿宽度
const baseFontSize = 100; // 方便计算的基准(1rem=100px)
function setRem() {
const width = Math.min(document.documentElement.clientWidth, designWidth);
// 限制最大宽度,避免大屏上字体过大
document.documentElement.style.fontSize = (width / designWidth * baseFontSize) + 'px';
}
// 初始化
setRem();
window.addEventListener('resize', setRem);
// 横屏切换也要处理
window.addEventListener('orientationchange', setRem);
这样设计稿上量出来是100px,你就写1rem;量出来是37px,写0.37rem。心算友好,且大屏有保护。
真·实战:用rem搭个移动端首页,em处理内部间距
来个完整的例子,一个移动端商品列表页。
HTML结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>商品列表</title>
<style>
/* 基础重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* rem基准设置 */
html {
font-size: 13.333333vw; /* 100/750*100,1rem=100px在750宽设备上 */
}
@media (min-width: 750px) {
html {
font-size: 100px; /* 大屏限制,避免无限放大 */
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
/* 用rem做整体边距 */
padding: 0.2rem;
}
/* 商品卡片 */
.product-card {
/* 外壳用rem,确保跨卡片一致 */
width: 100%;
margin-bottom: 0.2rem;
background: white;
border-radius: 0.12rem;
overflow: hidden;
/* 卡片内部字号基准 */
font-size: 0.28rem; /* 28px */
}
.product-image {
width: 100%;
height: 3.6rem; /* 360px,固定比例 */
object-fit: cover;
display: block;
}
.product-info {
/* 内部间距用em,相对于卡片的0.28rem */
padding: 0.714em; /* 0.2rem / 0.28rem ≈ 0.714 */
}
.product-title {
/* 标题字号用rem,绝对控制 */
font-size: 0.32rem; /* 32px */
font-weight: 600;
color: #333;
line-height: 1.4em; /* 相对于32px */
/* 间距用em */
margin-bottom: 0.5em; /* 16px */
/* 多行省略 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-meta {
display: flex;
justify-content: space-between;
align-items: center;
/* 相对于卡片的0.28rem */
margin-top: 0.5em;
}
.product-price {
/* 价格字号更大 */
font-size: 0.36rem; /* 36px */
color: #ff6b6b;
font-weight: 700;
/* 内部小标签用em */
.price-unit {
font-size: 0.6em; /* 相对于36px,21.6px */
margin-right: 0.2em;
}
.price-original {
font-size: 0.5em; /* 18px */
color: #999;
text-decoration: line-through;
margin-left: 0.5em;
}
}
.product-tag {
display: inline-block;
/* 标签内部用em,相对于父级 */
padding: 0.3em 0.6em;
font-size: 0.24rem; /* 24px */
background: #fff0f0;
color: #ff6b6b;
border-radius: 0.2em;
}
/* 按钮组件 – 完全用em,确保缩放协调 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
/* 所有尺寸基于自身字号 */
padding: 0.6em 1.2em;
font-size: 0.28rem; /* 默认28px */
border-radius: 0.8em;
border: none;
background: #ff6b6b;
color: white;
font-weight: 500;
}
.btn-small {
font-size: 0.24rem; /* 24px,其他自动缩放 */
}
.btn-large {
font-size: 0.32rem; /* 32px */
}
/* 底部固定栏 – 用rem确保占满宽度 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 1rem; /* 100px */
background: white;
border-top: 1px solid #eee; /* 边框用px保细 */
display: flex;
align-items: center;
padding: 0 0.3rem;
}
</style>
</head>
<body>
<div class="product-card">
<img src="product.jpg" alt="商品" class="product-image">
<div class="product-info">
<h3 class="product-title">2024新款夏季透气运动鞋男士休闲跑步鞋轻便舒适百搭潮流</h3>
<div class="product-meta">
<div class="product-price">
<span class="price-unit">¥</span>299<span class="price-original">¥599</span>
</div>
<span class="product-tag">热销</span>
</div>
<div style="margin-top: 0.3rem;">
<button class="btn btn-small">立即购买</button>
<button class="btn" style="margin-left: 0.2rem;">加入购物车</button>
</div>
</div>
</div>
<div class="bottom-bar">
<button class="btn btn-large" style="width: 100%;">结算 (2)</button>
</div>
</body>
</html>
看这个例子的设计思路:
- 布局骨架(width、height、position):用rem,响应式缩放
- 组件内部比例(padding、margin、border-radius):用em,相对于组件自身字号
- 字体大小:用rem,避免嵌套爆炸
- 边框:用px,保持细线清晰
老项目想切rem?小心这些隐藏雷区
迁移老项目不是简单替换单位,这些坑我踩过:
雷区一:第三方库样式崩了
用了Element UI、Ant Design这类库,它们内部可能是px。你一改根字号,它们的组件可能过大或过小。
解决方案:用CSS变量覆盖,或者只改业务代码,不改库样式。
/* 只让业务代码用rem,组件库保持原样 */
.my-business-page {
font-size: 0.16rem; /* 假设根字号被js控制 */
}
/* 覆盖特定组件 */
.el-button {
padding: 0.15rem 0.3rem !important;
}
雷区二:行内样式没改
老项目可能有大量style="width: 300px"的行内样式,搜都搜不全。
解决方案:用属性选择器批量处理(临时方案):
[style*="px"] {
/* 警告:这个选择器性能差,仅用于排查 */
outline: 2px solid red !important; /* 标红所有带px的行内样式 */
}
雷区三:JS计算的逻辑乱了
有些JS根据元素宽度做计算,比如轮播图、瀑布流。改rem后,获取到的offsetWidth变了,逻辑崩了。
解决方案:JS里也做单位转换,或者统一用getBoundingClientRect:
// 老代码
const width = element.offsetWidth; // 可能拿到rem计算后的值
// 新代码,获取实际像素
const rect = element.getBoundingClientRect();
const actualWidth = rect.width;
雷区四:打印样式出问题
打印时通常希望固定尺寸,rem可能导致打印出来过大或过小。
解决方案:打印媒体查询重置:
@media print {
html {
font-size: 12pt !important; /* 打印用pt单位 */
}
body {
width: 100% !important;
margin: 0 !important;
}
}
为什么有些团队禁用em?背后有苦衷
前面夸了em那么多,但确实有些团队(比如早期的Bootstrap团队)明确不推荐em,甚至禁用。原因很现实:
维护成本高
新人入职,看到padding: 1em,得先翻三层父级找font-size是多少,心智负担重。rem一眼就知道是相对于根字号。
设计规范难以落地
设计系统要求"按钮高度40px",用rem可以强制2.5rem,用em的话,按钮放不同上下文里高度会变,设计师要抓狂。
测试覆盖难
em的嵌套特性导致边界情况多,自动化测试难写。rem的行为更可预测。
但我的观点是:不是em不好,是用的地方不对。组件库、设计系统确实该用rem保稳定;但业务组件内部,em的比例优势不可替代。关键是约定大于配置,团队统一规范,哪些地方用em,哪些地方用rem,写进文档,别让人猜。
最后唠一句:单位只是工具,别被它绑架了思路
写到这突然想到,当年为了"到底用rem还是em"跟同事争得面红耳赤,现在想想挺傻的。单位只是实现设计的手段,不是目的。
选单位的核心逻辑:
- 需要全局统一控制的(布局、字体阶梯)→ 用rem
- 需要局部比例协调的(组件内边距、图标)→ 用em
- 需要绝对精确的(1px边框、阴影)→ 用px
- 需要流体响应的(字体平滑缩放)→ 用vw/vh配合clamp
甚至现在Container Queries出来了,还可以用cqi(container query inline size)这种相对于容器宽度的单位。CSS的世界越来越大,rem和em只是入门课。
最重要的是保持一致性。一个项目里混用三种单位没问题,但得有章法,别东一榔头西一棒槌。写注释、建文档、做Code Review,比纠结"哪个单位更先进"实在多了。
哦对了,如果你看完还是晕,记住这个作弊口诀:
布局用rem,比例用em,细线用px,流体用vw。 嵌套别用em算字体,组件内部em真香。 安卓记得防篡改,大屏限制别爆炸。
散会!去改代码吧,记得commit message写清楚"refactor: 统一单位系统",别让队友骂你。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。 一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤 非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!




