能,但需JS动态设置+视口约束配合;仅写死CSS自定义属性无法响应屏幕变化,必须用clientWidth计算缩放比并防抖更新,rem为主、calc()辅以混单位场景,注意兼容性与极值限制。

能,而且比写死一堆 rem 或 vw 值更可控、更易维护。 关键不是“能不能用 CSS 自定义属性”,而是它不能单独扛起适配逻辑——必须和 JS 动态设置 + 视口约束配合,否则只是把 magic number 换了个地方藏。
为什么不能只靠 :root 里写死 --base-font-size
很多人一上来就在 :root 里写 --base-font-size: 16px,然后所有 rem 都基于它。这在固定尺寸页面里没问题,但移动端要的是「随屏幕宽度缩放」。写死的值无法响应 window.innerWidth 变化,1rem 在 iPhone SE 和 iPad 上还是同一个物理像素,比例就崩了。
- 视口没设对(比如漏了
initial-scale=1),screen.width和document.documentElement.clientWidth就不等于设备逻辑宽度,JS 算出来的值会偏移 - 直接用
screen.width当设计稿基准(比如 375px)算缩放比,但某些安卓浏览器返回的是物理像素宽度,得除以window.devicePixelRatio才准 - CSS 自定义属性本身不触发重排重绘,JS 改了
style.setProperty('--base-font-size', '...')后,依赖它的rem元素不会自动 reflow,但浏览器会重新计算样式,所以视觉上是即时生效的——这点可以放心
如何用 JS 动态更新 --base-font-size 并绑定 resize
核心是把「设计稿宽度 → 实际根字号」的映射逻辑从 CSS 搬到 JS,再注入到自定义属性里。例如设计稿宽 750px,希望 750px 对应 100px 根字号(即 1rem = 100px),那公式就是:--base-font-size = (window.innerWidth / 750) * 100 + 'px'。
function updateRootFontSize() {
const width = document.documentElement.clientWidth || window.innerWidth;
const baseSize = (width / 750) * 100; // 750 是设计稿宽度
document.documentElement.style.setProperty('--base-font-size', `${baseSize}px`);
}
updateRootFontSize();
window.addEventListener('resize', () => {
// 加防抖,避免频繁触发
clearTimeout(window.__resizeTimer);
window.__resizeTimer = setTimeout(updateRootFontSize, 100);
});- 用
document.documentElement.clientWidth而非screen.width,它反映的是 CSS 像素下的视口宽度,更可靠 - 必须监听
resize,但原生事件太密,加 100ms 防抖是底线;如果还涉及横竖屏切换,建议额外监听orientationchange - 别忘了在页面加载完成前就调用一次
updateRootFontSize(),否则首屏渲染时--base-font-size还是初始值(通常是 16px)
rem 和 calc() 怎么配合自定义属性写样式
写了 --base-font-size 后,不能直接写 font-size: var(--base-font-size)——那只是把根字号复制一遍,没体现「倍数」关系。真正要用的是 rem 单位,它天然基于根字号,而根字号已由 JS 控制。
立即学习“前端免费学习笔记(深入)”;
但有些场景需要混合单位,比如 padding 左右用 vw、上下用 rem,这时就得靠 calc():
.card {
font-size: 1.4rem; /* 直接用 rem,自动跟随 */
padding: calc(var(--base-font-size) * 0.2) calc(2vw); /* 上下用根字号的 0.2 倍,左右用 vw */
max-width: calc(var(--base-font-size) * 5); /* 5rem 宽度,但用 calc 显式写出来源 */
}-
rem仍是主力,语义清晰:1rem = 当前根字号大小,无需重复计算 -
var(--base-font-size)只在需要「非整数倍」或「混单位」时才显式出现,比如calc(var(--base-font-size) * 0.8)比0.8rem更易读出设计意图(“这里是设计稿 80% 高度”) - 不要在
calc()里写var(--base-font-size) * 0.8 + 2px这种 px 混合,会破坏缩放一致性;所有绝对值都该转成相对单位
容易被忽略的兼容性和边界问题
这套方案在 iOS Safari 和 Chrome for Android 上基本稳,但老版本 UC、QQ 浏览器对 custom properties 的支持有 gap,且部分安卓 WebView 不触发 resize 事件(比如折叠屏展开时)。
- 最低支持:Chrome 49+、Firefox 31+、Safari 9.1+;IE 完全不支持,需降级 fallback(比如用媒体查询兜底)
- 极端小屏(如 320px 宽)下,算出来的
--base-font-size可能小于 12px,文字太小难读,建议加 min/max 限制:Math.max(12, Math.min(24, baseSize)) - 横屏时
clientWidth可能突变,但用户旋转设备后通常会手动缩放,此时 JS 更新的字号可能和视口实际缩放不一致——这是 viewport 锁定策略(user-scalable=no)和用户体验的权衡点,没法两全
真正麻烦的从来不是怎么写代码,而是设计稿给的标注单位(px)、开发用的单位(rem/vw)、测试看到的物理效果(dpi)三者之间始终存在隐性换算链。自定义属性只是让这条链的某一环更透明,而不是消灭它。


















