H5移动端响应式处理,折叠屏快速适配方案设计

介绍

我们在开发一款移动端 H5 应用的时候,往往不会使用响应式设计(开发成本问题),转而使用类似于 px2rem 的方案进行适配。

这种方案很便利,但是也有一些问题:

  • 大屏幕上显示大字体(用户:我买折叠屏不是为了看更大的字体!!!)
  • 横屏下,不仅是字体内容更大,所能展示的信息密度也会缩水

基于这些痛点,同时为了保持快速开发,这里设计了一个基于 iframe 开发的响应式折叠屏原型。

资源共享

主屏幕和副屏幕使用的是同一个应用,因为是同一份代码,能够共享样式、组件和 UI 库。同时为了能够快速拉取资源,还可以使用 PWA 进行打包,这样主副屏可以共享同一个 Service Worker 进行资源来取。

UI

框架通过 iframe 实现了主副屏之间的容器 UI 扩展:

  • 响应式布局,在大屏幕模式下显示双屏,小屏幕模式下显示单屏
  • 通过 window.top 绑定容器 UI,可跨屏幕进行全局展示。

页面布局代码:

html 复制代码
<script setup lang="ts">
import { getMainRouter, getSecondRouter } from "../utils";

const isSM = useMediaQuery("(min-width: 640px)");
const secondScreenPath = ref("");

watch(
  isSM,
  async (val) => {
    if (val) {
      // 从小屏幕 -> 大屏幕
      const mainRouter = getMainRouter();
      const currentRoute = mainRouter.currentRoute.value;
      if (currentRoute.meta.screen) {
        secondScreenPath.value = currentRoute.fullPath;
        await mainRouter.back();
      } else {
        secondScreenPath.value = "";
      }
    } else {
      // 从大屏幕 -> 小屏幕
      const mainRouter = getMainRouter();
      const secondRouter = getSecondRouter()!;
      if (secondRouter.currentRoute.value.meta.screen) {
        const currentRoute = secondRouter.currentRoute.value;
        mainRouter.push(currentRoute.path);
      }
    }
  },
  {
    flush: "pre",
  }
);
</script>

<template>
  <div
    class="h-screen w-screen grid sm:grid-cols-2 overflow-hidden grid-cols-1 gap-2"
  >
    <iframe
      name="main-screen"
      class="w-full h-full"
      src="./screen.html"
    ></iframe>
    <iframe
      v-if="isSM"
      name="second-screen"
      class="w-full h-full"
      :src="`./screen.html#${secondScreenPath}`"
    ></iframe>
  </div>
</template>

路由拦截

实现自动的路由跳转机制:

  • 自动识别可在副屏展示的路由(通过 meta.screen 配置)
  • 在主屏幕点击后自动将详情页在副屏打开
  • 屏幕尺寸变化时智能调整路由状态
    • 符合大屏时,副屏将主屏当前路由作为详情页打开,主屏回退到列表页
    • 符合小屏时,主屏将副屏路由作为新路由推入

主屏路由拦截:

ts 复制代码
router.beforeEach(async (to, from) => {
  /**
   * 当目标路由符合配置了 meta: true, 则代表其可以在副屏幕打开
   * 
   * 此时:
   * 如果是主屏幕,则跳转副屏幕打开
   * @param path 
   */
  if (to.meta.screen) {
    if (isMainScreen) {
      const router = getSecondRouter()
      if (router) {
        router.push(to.path);
        return false;
      }
    }
  }
});

响应式变化后,状态恢复

解决了屏幕尺寸变化后应用状态丢失的问题:

  • 保证用户体验连贯性,避免状态丢失
  • 使用 sessionStorage 保存页面状态(iframe内可共享)
  • 在屏幕模式切换时自动恢复之前的浏览状态
  • 不同 ID 的状态需隔离,同时需处理详情页 ID 相关的状态保存和恢复

状态管理:

ts 复制代码
const { id } = defineProps<{
  id: string;
}>();

const createDefaultDetailData = () => {
  return {
    showDialog: false,
    formData: {
      input1: "",
      input2: "",
    },
  };
};

/**
 * 不同的数据,需要绑定到不同的 sessionStorage 中
 *
 * 否则先点击 id 1,然后刷新页面,点击详情id 2。可能展示的是 id 1 的数据
 */
const detailData = useSessionStorage(
  () => `detail-${id}`,
  createDefaultDetailData()
);

// 在 useSessionStorage 之后执行
watch(
  () => id,
  () => {
    console.log("id changed", id);
    // id 变化了,说明详情内容变化了,需要重置表单,并清空非当前 id 的详情内容
    detailData.value = createDefaultDetailData();
    Object.keys(sessionStorage).forEach((key) => {
      if (key.startsWith("detail-") && key !== `detail-${id}`) {
        sessionStorage.removeItem(key);
      }
    });
  }
);

其他

相关推荐
小明爱吃瓜16 分钟前
AI IDE(Copilot/Cursor/Trae)图生代码能力测评
前端·ai编程·trae
不爱说话郭德纲21 分钟前
🔥Vue组件的data是一个对象还是函数?为什么?
前端·vue.js·面试
绅士玖24 分钟前
JavaScript 中的 arguments、柯里化和展开运算符详解
前端·javascript·ecmascript 6
GIS之路26 分钟前
OpenLayers 图层控制
前端
断竿散人26 分钟前
专题一、HTML5基础教程-http-equiv属性深度解析:网页的隐形控制中心
前端·html
星河丶27 分钟前
介绍下navigator.sendBeacon方法
前端
curdcv_po27 分钟前
🤸🏼🤸🏼🤸🏼兄弟们开源了,用ThreeJS还原小米SU7超跑!
前端
我是小七呦27 分钟前
😄我再也不用付费使用PDF工具了,我在Web上实现了一个PDF预览/编辑工具
前端·javascript·面试
G等你下课28 分钟前
JavaScript 中的 argument:函数参数的幕后英雄
前端·javascript
星河丶30 分钟前
前端如何判断用户设备
前端