基于 Flexible.js + postcss-px-to-viewport 的 REM 适配方案(支持系统缩放与浏览器缩放)
上一篇文章的进化版本
背景
在响应式布局开发中,px
单位逐渐被 rem
和 vw
替代,前者配合 flexible.js
适配不同设备的屏幕宽度,后者则直接依赖视口宽度。我们选择使用 rem
单位,是因为它具备更高的控制精度,且便于与字体缩放逻辑结合使用。同时,为了兼容系统级缩放(如 Windows 125%、150%)和浏览器缩放(Ctrl + / Ctrl -),我们对 flexible.js
进行了一次魔改,使其在设置根字体大小时,自动感知系统与浏览器缩放因子,从而保持页面在各种缩放设置下的视觉一致性。
核心思路
本方案由三部分组成:
- 使用
postcss-px-to-viewport
插件将px
自动转为rem
单位 - 魔改
flexible.js
,动态设置根节点的font-size
,感知系统与浏览器缩放 - 通过
zoom.ts
模块获取缩放信息,辅助计算准确的rem
值
1. 配置 postcss-px-to-viewport
我们将所有 px
转换为 rem
,并将视口宽度配置为设计稿宽度(如 1920px
)。这是 flexible.js
动态设置 rem
的基础。
// vite.config.ts
import postcsspxtoviewport from 'postcss-px-to-viewport';postcsspxtoviewport({unitToConvert: 'px',viewportWidth: 1920,unitPrecision: 5,propList: ['*'],viewportUnit: 'rem',fontViewportUnit: 'rem',selectorBlackList: [],minPixelValue: 1,mediaQuery: false,replace: true,exclude: undefined,include: undefined,landscape: false,landscapeUnit: 'rem',landscapeWidth: 1920,
});
- 注意:此处
viewportUnit
和fontViewportUnit
都设置为rem
,不是默认的vw
- 我们依赖 flexible.js 来动态控制
rem
与实际像素之间的比例。
2. 魔改 flexible.js:动态计算 rem
function refreshRem() {const { systemZoom, browserZoom } = getAllZoom();let width = document.documentElement.getBoundingClientRect().width;if (width / dpr > 540) {width = width * dpr;}let ratio = 100;if (systemZoom === 1) {ratio = 100;} else if (systemZoom > 1 && systemZoom <= 1.25) {ratio = 100 / systemZoom;} else if (systemZoom >= 1.5) {ratio = 120 / systemZoom;}let rem = width / ratio;rem = rem * browserZoom;document.documentElement.style.fontSize = rem + 'px';window.rem = rem;
}
调整逻辑说明:
systemZoom
:主要用于补偿系统设置导致的页面拉伸,比如 Windows 设置了 125%、150% 缩放。browserZoom
:确保用户主动浏览器放大(Ctrl +)时页面字体/布局也能自然放大。- 最终的
rem
:=viewportWidth / ratio
×browserZoom
3. 获取缩放信息:zoom.ts 模块
function getZoom() {if (window.devicePixelRatio !== undefined) {return getDecimal(window.devicePixelRatio);}if (window.outerWidth && window.innerWidth) {return getDecimal(window.outerWidth / window.innerWidth);}return 1;
}function getAllZoom() {const zoom = getZoom();const browserZoom = getDecimal(window.outerWidth / (window.innerWidth || 1));const systemZoom = getDecimal(zoom / browserZoom);return {zoom,browserZoom,systemZoom,systemResolution: Math.round(window.screen.width * systemZoom),};
}
- devicePixelRatio:总缩放倍数(系统 × 浏览器)
- outerWidth / innerWidth:用于反推出浏览器缩放倍数
- zoom / browserZoom:反推出系统缩放倍数
最终效果
- 页面布局单位使用
rem
,支持动态字体缩放; - 在系统级缩放(例如 Windows 150% 缩放)下页面保持正常比例;
- 用户 Ctrl + 放大页面时,布局和文字也能随着放大;
- 同时兼容高分屏和低分屏,兼顾开发体验和视觉一致性。
优势总结
方案 | 是否兼容系统缩放 | 是否兼容浏览器缩放 | 开发难度 | 控制粒度 |
---|---|---|---|---|
使用 vw | ❌ 视觉比例会偏差 | ✅ | 中 | 差(字体不易控) |
使用原生 rem | ❌ | ❌ | 简单 | 一般 |
本方案(rem + zoom) | ✅ | ✅ | 中 | 高(字体、布局都受控) |
注意事项
- 本方案在 Chrome、Edge 等现代浏览器下验证良好;
- 不适用于需要支持
< IE11
的场景; - 若使用 devtools 或双屏环境调试,缩放倍率检测可能受影响;
- 可结合 Tailwind CSS 或 PostCSS 构建体系使用。
结语
在多端适配越来越复杂的今天,仅依赖一种单位(如 vw 或 px)已经无法保证体验一致性。借助 rem
+ flexible.js
+ 浏览器缩放检测,我们构建了一个兼容性强、体验优秀的视觉适配方案,值得在中大型系统中推广应用。
如果你正在为视觉缩放一致性烦恼,不妨试试这个方案!