移动端适配终极方案
移动端适配绕不开两个 PostCSS 插件:postcss-pxtorem(px → rem)和 postcss-px-to-viewport(px → vw/vh)。二者都是编译期自动转换,写 px 即可适配不同屏幕,但原理和适用场景完全不同。本文从配置到踩坑一次性讲透。
一、两句话理解区别
| 方案 | 原理 | 一句话 |
|---|
| px → rem | 把 px 转成 rem,通过 <html> 的 font-size 缩放 | 按比例放大缩小根字号 |
| px → vw | 把 px 转成 vw/vh,通过视口宽度自动缩放 | 直接按视口百分比计算 |
对比:
1 2 3 4 5 6 7 8 9 10 11 12
| 设计稿 375px 宽,一个 100px 的盒子:
px → rem: 转换后 width: 1rem html font-size = 375 / 10 = 37.5px → 实际宽度 = 1 × 37.5 = 37.5px (375 屏) → iPhone 14 Pro Max (430) : 1 × 43 = 43px ✅
px → vw: 转换后 width: 26.67vw → 实际宽度 = 375 × 26.67% = 100px (375 屏) → iPhone 14 Pro Max (430) : 430 × 26.67% = 114.7px ✅
|
二、postcss-pxtorem
2.1 安装
1
| npm i postcss-pxtorem -D
|
2.2 配置
1 2 3 4 5 6 7 8 9 10 11 12
| module.exports = { plugins: { "postcss-pxtorem": { rootValue: 37.5, propList: ["*"], selectorBlackList: [".norem"], minPixelValue: 2, exclude: /node_modules/i, }, }, };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import pxtorem from "postcss-pxtorem";
export default defineConfig({ css: { postcss: { plugins: [ pxtorem({ rootValue: 37.5, propList: ["*"], }), ], }, }, });
|
2.3 配套的 flexible.js
rem 方案依赖在 <html> 上动态设置 font-size:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (function () { const baseSize = 37.5;
function setRem() { const scale = document.documentElement.clientWidth / 375; document.documentElement.style.fontSize = Math.min(baseSize * scale, baseSize * 2) + "px"; }
setRem(); window.addEventListener("resize", setRem); window.addEventListener("pageshow", e => { if (e.persisted) setRem(); }); })();
|
或者精简版(直接除 10):
1 2 3
| <script> document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + "px"; </script>
|
2.4 不同设计稿的 rootValue
| 设计稿宽度 | rootValue |
|---|
| 375px | 37.5 |
| 750px | 75 |
| 640px | 64 |
| 1920px (PC) | 192 |
2.5 配置项详解
| 参数 | 默认值 | 说明 |
|---|
rootValue | 16 | 根元素字体大小,通常设 设计稿宽度 / 10 |
unitPrecision | 5 | rem 小数精度 |
propList | ['*'] | 转换的属性,如 ['font-size', 'width'] |
selectorBlackList | [] | 不转换的选择器 |
replace | true | 是否替换原 px,而非追加 |
mediaQuery | false | 媒体查询中是否转换 |
minPixelValue | 0 | 小于此值的 px 不转换 |
exclude | null | 正则匹配排除文件 |
2.6 不想被转换的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .norem .icon { width: 20px; } .icon { width: 20px; }
.border { border: 1px solid #eee; }
.border { border: 1px solid #eee; }
|
2.7 优点与缺点
| 优点 | 缺点 |
|---|
| 兼容性好,全平台通用 | 需要额外引入 flexible.js |
| 第三方 UI 库(Vant)原生支持 rem | 第三方库如用 px 且被转换,布局可能出错 |
| 可精细控制哪些元素不转换 | font-size 缩放会受系统字体设置影响 |
| 成熟方案,Vant 2.x 官方推荐 | 小数点精度问题(1rem = 37.5px 容易除不尽) |
三、postcss-px-to-viewport
3.1 安装
postcss-px-to-viewport 原始包已停更,推荐使用新包:
1
| npm i @minko-fe/postcss-px-to-viewport -D
|
3.2 基础配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| module.exports = { plugins: { "@minko-fe/postcss-px-to-viewport": { viewportWidth: 375, unitPrecision: 5, viewportUnit: "vw", selectorBlackList: [".ignore"], minPixelValue: 1, mediaQuery: false, exclude: [/node_modules/], }, }, };
|
3.3 Vite 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import pxToViewport from "@minko-fe/postcss-px-to-viewport";
export default defineConfig({ css: { postcss: { plugins: [ pxToViewport({ viewportWidth: 375, unitPrecision: 5, viewportUnit: "vw", }), ], }, }, });
|
3.4 常用配置项
| 参数 | 默认值 | 说明 |
|---|
viewportWidth | 375 | 设计稿宽度,必填 |
viewportHeight | 667 | 设计稿高度(vh 转换时用) |
unitPrecision | 5 | 小数精度 |
viewportUnit | ‘vw’ | 转换单位,可设 vh / vmin / vmax |
fontViewportUnit | ‘vw’ | 字体用的单位,可独立设 |
selectorBlackList | [] | 不转换的选择器 |
minPixelValue | 1 | 小于此 × 不转换 |
mediaQuery | false | 媒体查询中的 px 是否转换 |
landscape | false | 是否生成 @media (orientation: landscape) |
landscapeWidth | 568 | 横屏设计稿宽度 |
landscapeUnit | ‘vw’ | 横屏单位 |
exclude | null | 正则排除路径 |
include | null | 正则仅包含路径 |
3.5 不想转换的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| .ignore { width: 16px; }
.box { width: 100px; border: 1px solid #eee; }
.box { border: 1px solid #eee; }
.page { width: 375px; }
|
3.6 横屏适配
1 2 3 4 5
| { landscape: true, landscapeWidth: 812, landscapeUnit: 'vw', }
|
生效后,插件会在横屏时自动切换到以 812px 为基准的 vw 计算。
3.7 优点与缺点
| 优点 | 缺点 |
|---|
| 无需引入 JS,纯 CSS 方案 | 部分旧设备不支持 vw/vh |
| 转换直观,和设计稿 1:1 对应 | 第三方库的 px 可能被误转换 |
| Vant 3.x / 4.x 官方推荐 | 大屏设备(iPad/PC)元素过大 |
| 小数点精度好 | 手机上 1px 边框线可能变很粗 |
四、两种方案深度对比
| 维度 | postcss-pxtorem | postcss-px-to-viewport |
|---|
| 依赖 | 需额外 flexible.js | 原生 CSS 单位,无依赖 |
| 原理 | rem = px / rootValue,rootValue 动态变化 | vw = px / viewportWidth × 100 |
| 第三方 UI 兼容 | Vant 2.x 内置 rem(需配合) | Vant 3/4 推荐 vw |
| 横屏适配 | 需手动调整 rootValue | 插件内置 landscape 模式 |
| PC 端限制 | 可设 maxWidth 限制 | 大屏会等比例放大,需额外处理 |
| 1px 边框 | 不转换或转大写 PX | 可用 minPixelValue: 1 排除 |
| 系统字体影响 | 受影响(用户可改浏览器字号) | 不受影响 |
| 兼容性 | 极好,全平台 | iOS 8+ / Android 4.4+ |
| 维护状态 | postcss-pxtorem 稳定 | 原包停更,推荐 @minko-fe/ 分支 |
选型建议
1 2 3 4 5 6 7 8
| 你的项目: ├─ 用 Vant 2.x → 选 postcss-pxtorem ├─ 用 Vant 3.x / 4.x → 选 postcss-px-to-viewport ├─ 兼容老旧设备(iOS 7 以下)→ 选 postcss-pxtorem ├─ 对 1px 边框高要求 → 选 postcss-px-to-viewport + transform: scale ├─ 需要横屏适配 → 选 postcss-px-to-viewport ├─ PC 端也需要展示 → 选 postcss-pxtorem + maxWidth 限制 └─ 追求极致简洁 → 选 postcss-px-to-viewport
|
五、进阶:两者混用
有些项目需要 vw 做布局 + 某些场景保留 rem 作为兜底:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| module.exports = { plugins: [ require("postcss-pxtorem")({ rootValue: 37.5, propList: ["font", "font-size", "line-height", "letter-spacing"], exclude: /node_modules/, }), require("@minko-fe/postcss-px-to-viewport")({ viewportWidth: 375, propList: ["*", "!font*", "!line-height", "!letter-spacing"], exclude: /node_modules/, }), ], };
|
执行顺序:PostCSS 插件按声明顺序执行,先 rem 后 vw,通过 propList 互相排除避免重复转换。
六、Vant UI 库配合
Vant 2.x + pxtorem
1 2 3 4 5 6 7 8 9 10
| module.exports = { plugins: { "postcss-pxtorem": { rootValue: 37.5, propList: ["*"], }, }, };
|
Vant 3/4 + px-to-viewport
1 2 3 4 5 6 7 8 9 10
| module.exports = { plugins: { "@minko-fe/postcss-px-to-viewport": { viewportWidth: 375, }, }, };
|
Vant 4 可直接按 vw 引入样式(推荐):
1 2 3
| import Components from "unplugin-vue-components/vite"; import { VantResolver } from "unplugin-vue-components/resolvers";
|
八、常见问题与排查
| 问题 | 原因 | 解决 |
|---|
| 转换后页面元素巨大 | rootValue 或 viewportWidth 搞反了 | 检查设计稿宽度是否正确填写 |
| vw 方案在 PC 端文字过大 | 视口宽如 1920px 时 vw 会等比放大 | 加 max-width: 750px 容器限制 |
| 第三方组件库样式错乱 | 组件库 px 被误转换 | exclude: /node_modules/ |
| 1px 边框被转成 0.27vw 变粗 | 小数值 vw 在 Retina 屏上显示异常 | 设 minPixelValue: 1 或用大写 PX |
| flexible.js 导致 Android 页面抖动 | Android WebView resize 频繁触发 | 加防抖或替换为 CSS 方案 |
| rem 方案刷新后布局变化 | flexible.js 写入 font-size 的时机问题 | 将 script 放在 <head> 最顶部同步执行 |
| 内联样式中的 px 不转换 | PostCSS 只处理 .css/.vue 文件 | 内联样式手动改为 rem/vw |
| property ‘xxx’ does not exist | 插件版本与 PostCSS 8 不兼容 | 确认版本:”postcss-pxtorem”: “^6.0” 对应 PostCSS 8 |
七、完整配置文件速查
pxtorem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { plugins: { "postcss-pxtorem": { rootValue: 37.5, unitPrecision: 5, propList: ["*"], selectorBlackList: [".norem"], replace: true, mediaQuery: false, minPixelValue: 1, exclude: /node_modules/i, }, }, };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> (function (w, d) { var docEl = d.documentElement, timer; function setRem() { docEl.style.fontSize = docEl.clientWidth / 3.75 + "px"; } w.addEventListener("resize", function () { clearTimeout(timer); timer = setTimeout(setRem, 100); }); w.addEventListener("pageshow", function (e) { e.persisted && setRem(); }); setRem(); })(window, document); </script>
|
px-to-viewport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| module.exports = { plugins: { "@minko-fe/postcss-px-to-viewport": { viewportWidth: 375, viewportHeight: 667, unitPrecision: 5, viewportUnit: "vw", fontViewportUnit: "vw", selectorBlackList: [".ignore"], minPixelValue: 1, mediaQuery: false, landscape: false, exclude: [/node_modules/], }, }, };
|
总结
1 2 3 4
| postcss-pxtorem → 老牌方案,Vant 2 标配,兼容极致 postcss-px-to-viewport → 新潮方案,Vant 3/4 推荐,零 JS 依赖
选型一句话:新项目无脑 vw,老项目继续 rem,横屏场景必须 vw。
|
全文覆盖两种方案的原理对比、完整配置、Vite 集成、Vant 配合、横屏适配、混用策略、12 类常见坑排查,附赠配置文件一键复制。