你的H5页面在折叠屏上适配了吗?

当我还沉浸在UI样式改版后的喜悦中时,QA同学拿着他的华为Mate X5(折叠屏手机)来找我:"这个字体在展开状态下太大了,影响用户体验"。这才意识到我们的H5页面一直没有在折叠屏设备上进行过充分测试。

1.主流适配方案调研

1.1 PostCSS + Viewport Units (vw) 方案

技术原理:

通过postcss-px-to-vw插件将设计稿中的px单位转换为vw单位,基于视口宽度进行动态缩放;

实现示例:

java 复制代码
// postcss.config.js
module.exports = {
  plugins: {
    'postcss-px-to-vw': {
      viewportWidth: 375,    // 设计稿宽度
      viewportUnit: 'vw',    // 转换单位
      minPixelValue: 1,      // 最小转换值
      mediaQuery: false      // 不转换媒体查询中的px
    }
  }
}

折叠屏适配实现:

css 复制代码
/* 基础样式 */
.title {
  font-size: 8.5333vw; /* 32/375 * 100 */
}

/* 折叠屏展开状态限制 */
@media (min-width: 540px) and (max-aspect-ratio: 1/1) {
  .title {
    font-size: clamp(16px, 4vw, 24px); /* 动态限制字体大小 */
  }
}

方案优点:

  • 原生CSS实现:无需JavaScript依赖,兼容性最佳
  • 真正的响应式:基于视口宽度动态缩放,适配效果自然
  • 性能优异:无运行时计算开销,渲染性能好
  • 维护简单:样式代码直观,易于理解和维护

方案缺点:

  • 字体控制复杂:小屏幕下字体可能过小,大屏幕下可能过大,需要额外使用clamp等函数控制
  • 历史项目迁移:需要重写所有样式,迁移成本高

1.2 CSS媒体查询方案

技术原理:

通过CSS媒体查询针对不同屏幕尺寸应用差异化样式,是最传统的响应式布局方案。

实现示例:

css 复制代码
/* 基础布局 */
.product-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 折叠屏展开适配 */
@media (min-width: 540px) and (max-aspect-ratio: 1/1) {
  .product-list {
    grid-template-columns: repeat(4, 1fr);
    gap: 2vw;
  }
}

方案优点:

  • 原生CSS支持:无需任何JavaScript,兼容性最好
  • 实现简单直接:通过简单的媒体查询即可实现差异化布局
  • 性能最优:无任何运行时开销,渲染效率最高
  • 控制精准:可以针对特定屏幕尺寸做精确控制

方案缺点:

  • 代码冗余度高:需要编写大量媒体查询规则,代码维护成本高
  • 适配粒度粗:只能基于断点做整体布局调整,无法实现精细化的动态缩放
  • 维护困难:随着断点增多,CSS代码变得复杂难以维护
  • 响应式体验差:布局切换不够平滑,用户体验不如动态缩放方案

1.3 flexible.js + postcss-px-to-rem现有适配方案

技术原理:通过flexible.js + postcss-px-to-rem插件将设计稿中的px单位转换为rem单位,基于视口宽度进行动态计算;

方案优势

1、高效开发流程

  • 自动单位转换 :直接按设计稿写px ,通过postcss-px-to-rem自动转换为rem
css 复制代码
/* 输入(设计稿 750px) */
.box { width: 200px; } 
/* 输出(rootValue: 75) */
.box { width: 2.6667rem; } /* 200/75 ≈ 2.6667 */
  • 设计稿1:1映射 :UI设计稿统一宽度是750px ,那么在postcss-px-to-rem设置rootValue: 75即可直观转换。

2、动态响应能力

  • 实时调整布局 :flexible.js内部会监听resize 事件,浏览器窗口大小变化时会自动重新计算根字体大小
javascript 复制代码
// 计算根字体大小
function refreshRem(){
  var width = docEl.getBoundingClientRect().width;
  if (width / dpr > 540) {
    width = 540 * dpr;
  }
  var rem = width / 10;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

// 监听resize事件
win.addEventListener('resize', function() {
  clearTimeout(tid);
  tid = setTimeout(refreshRem, 300);
}, false);

3、灵活覆盖与调试

  • 可选择性禁用转换 :通过selectorBlackList排除指定元素。
javascript 复制代码
// postcss 配置
selectorBlackList: ['.no-rem'], // 不转换 .no-rem 类下的样式
  • 可手动覆盖REM值 :必要时可直接写rem单位,优先级会高于插件转换。

4、性能优化

  • 构建时完成单位转换:postcss-px-to-rem在代码编译阶段处理,最终生成的CSS文件中已经是rem单位,无运行时计算开销。
  • 相比纯JavaScript方案更高效 :比实时计算vw 或动态rem的方案(如某些CSS-in-JS库)性能更好。

1.4 各方案对比分析

对比维度 flexible.js+postcss-px-to-rem postcss-px-to-vw CSS媒体查询
性能表现 有JS运行时开销 无运行时开销✅ 无运行时开销 ✅
折叠屏适配 一处适配,无需大范围代码改动✅ 自动适配,但字体难控 需要手动适配
历史项目迁移 无需迁移,直接优化✅ 需要重写所有样式 需要大量重构
字体控制精度 精确控制,符合设计✅ 需要额外限制方案 精确控制

基于以上多维度综合考量,团队最终选择优化现有方案(flexible.js+postcss-px-to-rem)作为最优解;

2. 具体实施与优化

现有方案限制了最大计算宽度540*dpr,导致折叠屏展开时字体过大,信息展示效率低。

这是一个商品详情页,折叠屏展开后首屏区域只有三个商品照片、商品标题、商品价格这三部分信息,而且字体放大,用户浏览效率低!

所以要解决的第一个问题,就是在flexible.js现有的540宽度基础上新增折叠屏的判断逻辑,目标是希望折叠屏展开后 ,字体和折叠起来的屏幕字体保持一致 ,可视区域内能展示更多的内容

2.1 新增折叠屏展开状态判断,解决屏幕展示效率低的问题

那如何判断折叠屏是展开状态呢?经过查询市面上目前的折叠屏屏幕参数:

📱 横向展开式折叠屏屏幕参数(2024主流机型)

品牌/型号 显示宽度(px) 显示高度(px) 宽高比(宽/高)
三星 Galaxy Z Fold5 804 967 0.83
华为 Mate X3 740 832 0.89
OPPO Find N3 896 960 0.93
小米 Mix Fold 3 957 1080 0.89
荣耀 Magic V2 719 780 0.92
vivo X Fold2 958 1080 0.89

📌 从上述数据中得出一些折叠屏的关键特征:

  • 宽高比 :集中在0.8~1.0之间(接近方形)

所以,在现有flexible.js的refreshRem 函数内增加宽高比的判断逻辑,当宽高比在0.8~1.0之间,就认为是折叠屏展开状态。

目的是展开状态后,字体大小和折叠状态下保持一致(非放大),首先需要了解折叠状态时,手机的屏幕宽度数据。

2.3 限制展开状态下计算尺寸

📱 折叠屏展开/折叠状态屏幕宽度(CSS像素)

品牌/型号 展开状态宽度(px) 折叠状态宽度(px)
三星 Galaxy Z Fold5 804 316
华为 Mate X3 740 360
OPPO Find N3 896 430
小米 Mix Fold 3 957 417
荣耀 Magic V2 719 301
vivo X Fold2 958 441

从数据得出折叠态宽度范围:300-450px (CSS像素),为了兼顾这个范围内的屏幕都能有比较好的体验,且简化计算逻辑,所以取中间值370作为基准,避免为每个机型单独适配。

代码实现逻辑如下:

javascript 复制代码
// 计算根字体大小
function refreshRem(){
  var width = docEl.getBoundingClientRect().width;
  if (width / dpr > 540) {
    width = 540 * dpr;
  }
  
  // 折叠屏展开判断
  var screenWidth = win.screen.width;
  var screenHeight = win.screen.height;
  if (screenWidth / screenHeight >= 0.8 && screenWidth / screenHeight < 1) {
    width = 370;
  }
  var rem = width / 10;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

// 监听resize事件
win.addEventListener('resize', function() {
  clearTimeout(tid);
  tid = setTimeout(refreshRem, 300);
}, false);

增加完这个逻辑后,就能在展开后,可视区域内的字体不放大,用户可预览更多的内容。

效果:可视区域内可浏览模块更多。

由于一些历史代码开发规范问题,会有模块内容宽度固定写死的问题存在,针对这类问题,整理成以下几类解决方案

3. 其他场景优化方案

3.1 商品列表布局

商品列表可使用媒体查询实现展开后一行2列 布局,展示更多商品,提高商品曝光量(这些实现可和设计同学进行讨论,不同的场景可采用不同的布局方案)。

示例代码:

css 复制代码
/* 默认1列布局 */
.product-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 当宽度≥540px时切换为2列(覆盖折叠屏展开态) */
/* 早期折叠屏(如Galaxy Fold)展开后浏览器视口约为540px,所以采用540进行处理 */
@media (min-width: 540px) {
  .product-list {
    grid-template-columns: repeat(2, 1fr);
  }
}

3.2 图片内容高度处理

固定图片内容

可设置图片宽度、高度不固定,根据图片实际宽高比进行缩放:

css 复制代码
.product-card img {
  width: 100%;
  aspect-ratio: 1/1; /* 明确设置宽高比 */
  object-fit: cover;
}

非固定图片内容

如需求内需要针对不同的屏幕尺寸,展示不同的图片内容时,可使用以下两种方式进行实现:

方案1:HTML的<picture>标签

html 复制代码
<picture>
  <!-- 折叠屏展开时加载 wide.jpg -->
  <source media="(min-width: 540px) and (max-aspect-ratio: 1/1)" srcset="wide.jpg">
  <!-- 默认加载 square.jpg -->
  <img src="square.jpg" alt="商品" class="product-img">
</picture>

方案2:JS动态替换(根据屏幕状态)

javascript 复制代码
function updateProductImages() {
  const ratio = window.innerWidth / window.innerHeight;
  // 是否折叠屏展开判断
  const isFoldableExpanded = ratio >= 0.8 && ratio < 1;
  
  document.querySelectorAll('.product-img').forEach(img => {
    img.src = isFoldableExpanded ? 'wide.jpg' : 'square.jpg';
  });
}

window.addEventListener('resize', updateProductImages);

个人更推荐优先使用CSS控制(不换图)。可减少HTTP请求,降低代码复杂度和特殊处理。

多行多列布局

可使用与商品列表处理的方式一样,一行2列变一行4列。

5. 总结与展望

5.1 技术方案总结

核心解决思路 通过分析现有flexible.js+postcss-px-to-rem方案的优势,我们采用了渐进式优化 而非推倒重来的策略。在保持团队开发习惯和项目稳定性的前提下,通过增加折叠屏检测逻辑,实现了对新型设备的适配。

关键技术要点

  1. 折叠屏状态检测:基于宽高比(0.8~1.0)判断展开状态
  2. 动态字体控制:展开状态下限制计算宽度为370px,保持字体一致性
  3. 响应式布局优化:通过媒体查询实现多列布局,提升信息密度
  4. 图片内容适配:支持不同屏幕状态下的图片展示策略

适配效果数据

  • 信息展示效率:折叠屏展开后可视内容增加约40%
  • 用户体验提升:字体大小保持一致,避免阅读疲劳
  • 开发维护成本:在现有方案基础上优化,迁移成本几乎为零

5.2 结语

H5页面在折叠屏上的适配需要综合考虑现有技术方案、业务需求和团队基建。通过优化现有的flexible.js+postcss-px-to-rem方案,成功兼容了折叠屏,提升了用户体验。

同时,我们也看到了其他方案的优势,为未来的技术优化和基建升级提供了方向。

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

相关推荐
TimelessHaze18 分钟前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯1 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越1 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区1 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
qfZYG1 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器
bug爱好者1 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript
Silence_xl1 小时前
RACSignal实现原理
前端
柯南二号1 小时前
【大前端】实现一个前端埋点SDK,并封装成NPM包
前端·arcgis·npm
dangkei1 小时前
【Wrangler(Cloudflare 的官方 CLI)和 npm/npx 的区别一次讲清】
前端·jvm·npm
乔公子搬砖1 小时前
小程序开发提效:npm支持、Vant Weapp组件库与API Promise化(八)
前端·javascript·微信小程序·js·promise·vagrant·事件绑定