记录 lhx-kit H5 适配方案从 0 到 1 的完整决策路径。 每一个看似"常识"的选择,都是在其他方案被证否后剩下的那一个。
同一个 H5 产品需要同时在以下环境看起来像是专门为那个环境做的:
| 场景 | 典型尺寸 | 约束 |
|---|---|---|
| 📱 iPhone SE | 375 × 667 | 小屏,文字不能太小 |
| 📱 iPhone 15 Pro Max | 430 × 932 | 大屏,元素不能太分散 |
| 📱 Android 各机型 | 360 – 428 | 最常见范围 |
| 📱 iPad 竖屏 | 768 × 1024 | 平板,元素不能过大 |
| 🖥️ 桌面浏览器 | 1440+ | 不能崩,但可以是"放大的手机壳" |
设计师交付 750px 宽的 2x 设计稿(业界事实标准,中国团队尤甚)。
业界四条主流路径。
浏览器把 750 当 CSS viewport 宽度,缩放到实际屏幕。
| 评价 | |
|---|---|
| ✅ | 零改造:CSS 里写 width: 100px 就是 750 画布上的 100px |
| ❌ | 文字也被缩放,325px 小屏上 14px 文字渲染成 ~6px |
| ❌ | 响应式边界失效(@media (min-width: 500px) 不再对应物理屏幕) |
| ❌ | 微信 / 部分 WebView 对 width=N 支持不稳定 |
| 评价 | |
|---|---|
| ✅ | 标准 CSS,现代浏览器 100% 支持 |
| ✅ | 响应式规则照常工作 |
| ❌ | 开发只能写 vw,对手工编码心智负担大 |
| ❌ | Vant / antd-mobile 等组件库内部是 px,需要 postcss 插件转换 |
| ❌ | iOS Safari 的 vh 有 bfcache 刷新 bug |
重型项目会踩到组件库边界。
CSS 里写 rem,但通过 postcss 插件把 px 自动转成 rem。
| 评价 | |
|---|---|
| ✅ | 开发只写 px,心智零负担 |
| ✅ | 组件库(Vant 等)用 rem 的也兼容 |
| ✅ | 设计稿 750 + rootValue: 75 → 完美 1:1 |
| ❌ | 桌面浏览器打开 fontSize 会爆(1920 / 10 = 192px) |
| ❌ | 需要 JS 运行时 + postcss 构建插件,双重依赖 |
@media 断点| 评价 | |
|---|---|
| ✅ | 无运行时 |
| ❌ | 断点数量爆炸(手机+平板+折叠屏) |
| ❌ | 跨断点的中间尺寸无法平滑过渡 |
rem (lib-flexible) + postcss-pxtorem + 桌面居中护栏
也就是方案 3,加上对"桌面浏览器打开"的兜底。
vw 和 rem 在功能上基本等价(数学公式几乎相同)。决策因素:
| 场景 | vw 痛点 | rem 优势 |
|---|---|---|
| 心智负担 | 写 13.33vw 反直觉 |
写 100px 直觉 |
| 组件库 | Vant 内部混用 px/rem,vw 需要复杂 postcss 映射 | rem 原生兼容 |
| iOS Safari 动态工具栏 | vh 受动态工具栏影响,计算不稳定 |
rem 根据 clientWidth 计算,稳定 |
| 已有工程迁移 | 现有 px 代码必须全改 | 开箱即用 |
lib-flexible 本来是为纯移动设计的。现实:
不处理的后果:
:::code-group
:::
我们选方案 B。
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
enableRem |
boolean |
true |
主开关。admin 项目传 false |
maxWidth |
number | false |
false |
启用桌面居中 + rem 上限。推荐 750 |
safeArea |
boolean |
true |
注入 env(safe-area-inset-*) → --lhx-safe-* CSS 变量 |
ensureViewport |
boolean |
true |
自动保证 viewport meta 存在 |
detectHairlines |
boolean |
true |
检测 0.5px 边框支持,DPR>=2 时给 html 加 .hairlines |
deviceOverrides |
function |
– | 针对 iPad / 鸿蒙等设备的定制调整 |
rootValue: 75:750 设计稿除 10exclude: /node_modules/:不转换第三方库的 px(否则 Vant 等组件内部坐标会错乱)propList: ['*']:所有属性都转(默认只转字体相关)
::::::danger 很多 rem 翻车的项目忽略这一点
浏览器 UA 样式表里,h1/h2/p/ul/ol/blockquote 等元素的 margin/padding/font-size 默认用 em 单位。
如果不 reset:
<h1> 默认 font-size: 2emem 不是 rem——它是相对父元素的 font-size2em 就是 2 * clientWidth / 10 = 巨大
:::必须至少 reset 这些:
examples/vmpa 和 examples/rmpa 的 bootstrap 文件会 import './styles/reset.css',脚手架生成项目时自动包含。
:::details 问题与修复
现象:orientationchange 事件触发时 document.documentElement.clientWidth 还是旧值。
解决:在 orientationchange handler 里设 setTimeout(updateRem, 300) 二次触发。
这个 trick 从 lib-flexible 源码继承。
现象:页面从 back/forward cache 恢复时,inline style 不会自动重新执行。
解决:监听 pageshow 事件的 persisted 字段,恢复时重新计算。
现象:第一次 JS 执行前 html font-size 还是 16px,那几毫秒里 CSS 用 rem 的元素会渲染成"默认尺寸",JS 执行完立即正确,造成闪烁。
解决:把初始化脚本 inline 到 <head> 里,在任何 CSS 加载前执行:
模板里的 template.html 已经包含这一段。
现象:Chrome DevTools 的 "Responsive" 模式有时报告 clientWidth === 0(特别是首次进入时)。
解决:
如果你要在一个已有的非 lhx-kit 项目里落地:
整个迁移约半天。
:::warning 下列场景不适合
@media 断点即可,不需要 rem