rem是相对于根元素字体大小的单位,需通过JS动态设置html的font-size并乘以dpr修正,才能实现跨设备视觉一致;直接写font-size: 16px会使rem失去响应能力。

rem 是什么,为什么不能直接写 font-size: 16px?
rem 是相对于根元素(<html>)字体大小的单位。它本身不“适配”,真正起作用的是你动态改 document.documentElement.style.fontSize 的逻辑。很多人一上来就套用网上“750px 设计稿 / 100 = rem 基准”,却没想清楚:这个 100 是怎么来的?它只在设计稿宽度为 750px、目标设备屏幕宽度为 750px 时才等于 1rem = 1px —— 显然不成立。实际中,你要的是“让 1rem ≈ 设备独立像素(dpr=1 时的 CSS px)”,才能保证视觉一致性。
- 直接写死
html { font-size: 16px },所有 rem 都失去响应能力 - 不考虑
window.devicePixelRatio,高倍屏下文字/控件会变小(因为 CSS px 数量没变,但物理像素更密) - 用
vh/vw混合 rem,容易在 iOS Safari 横竖屏切换时触发重排错乱
最简可用的 JS 动态设置方案(兼容 iOS/Android)
别碰 lib,手写 20 行足够。核心是:以设备屏幕宽度为基准,按比例缩放根字号,并乘以 dpr 修正物理渲染尺寸。注意不是“放大 dpr 倍”,而是让 1rem 在 dpr=2 的屏上也撑开约 2 倍物理像素,从而保持和 dpr=1 下相近的视觉大小。
function setRem() {
const baseWidth = 375; // 以 iPhone 6/7/8 宽度为基准(设计稿常用)
const scale = document.documentElement.clientWidth / baseWidth;
const dpr = window.devicePixelRatio || 1;
document.documentElement.style.fontSize = `${scale * 100 * dpr}px`;
}
setRem();
window.addEventListener('resize', setRem);
window.addEventListener('pageshow', e => e.persisted && setRem());-
baseWidth必须和设计稿宽度一致(如 750px 就改成750,同时把100改成200) -
pageshow事件捕获浏览器前进/后退缓存恢复,否则 iOS Safari 切页后 rem 失效 - 不要用
orientationchange:iOS 横竖屏切换时clientWidth不实时更新,优先靠resize
CSS 中 rem 使用的三个硬约束
写了 JS 设置,不代表样式能自动适配。rem 只作用于字体、宽高、内边距等接受长度值的属性,且必须确保没有被其他规则覆盖。
- 所有字体大小必须用
rem,禁用px和em(em相对于父元素,链式继承极易失控) - 容器宽高建议用
rem,但避免对input、textarea等表单控件设固定height,它们在 Android 上可能因系统字体缩放失效 - 绝对定位的
top/left必须用rem,否则在不同屏宽下偏移量失真;慎用%+rem混合,计算逻辑不直观
调试时必看的两个 Chrome DevTools 技巧
rem 适配出问题,90% 是根字号没生效或被覆盖。别猜,直接看真实计算值。
立即学习“前端免费学习笔记(深入)”;
- 在 Elements 面板选中
<html>元素,右侧 Styles 标签页里搜font-size,确认是否显示类似font-size: 200px(dpr=2 时) - 右键任意使用了
rem的元素 → “Computed” → 找font-size行,点开小箭头看“resolved to”,它会告诉你最终换算成多少px,比肉眼判断准得多 - 如果看到
font-size: 16px(浏览器默认值)且没被覆盖,说明 JS 没执行、执行报错、或执行时机太早(比如放在<head>里但 DOM 还没 ready)
最麻烦的不是写不对,是忘了 dpr 修正后,1rem 对应的物理尺寸其实变大了——这意味着你在 dpr=2 的屏上,用 0.1rem 边框可能根本看不见。这种细节,只有真机连调试器反复调过才会记住。



















