欢迎光临
我们一直在努力

前端小白别再搞混了:rem和em到底咋用才不翻车(附实战避坑指南)

在这里插入图片描述

前端小白别再搞混了: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,能统计全站用了哪些单位、颜色、字体。一眼看出单位混乱程度。

步骤三:渐进式替换策略

别一股脑全改,按优先级:

  • 先统一根字号:确保html的font-size稳定
  • 布局相关改rem:width、max-width、margin、padding等
  • 字体大小逐步迁移:先改标题,再改正文
  • 保留特定场景的px:边框、阴影等
  • 步骤四:用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等工具

    吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤 非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 前端小白别再搞混了:rem和em到底咋用才不翻车(附实战避坑指南)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址