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);
      }
    });
  }
);

其他

相关推荐
不和乔治玩的佩奇几秒前
【 设计模式】常见前端设计模式
前端
bloxed6 分钟前
vue+vite 减缓首屏加载压力和性能优化
前端·vue.js·性能优化
打野赵怀真18 分钟前
React Hooks 的优势和使用场景
前端·javascript
HaushoLin23 分钟前
ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss
前端·vue.js·css3·html5
Lafar23 分钟前
Widget 树和 Element 树和RenderObject树是一一 对应的吗
前端
小桥风满袖24 分钟前
炸裂,前端神级动效库合集
前端·css
匆叔25 分钟前
Tauri 桌面端开发
前端·vue.js
1_2_3_26 分钟前
react-antd-column-resize(让你的table列可以拖拽列宽)
前端
Lafar26 分钟前
Flutter和iOS混合开发
前端·面试