概述

应用使用H5开发的历史页面多且冗杂,同时H5相较于ArkUI一多缺乏拿来即用的适配方案。为达到HarmonyOS原生一多体验,所有页面进行一多适配复杂且工作量大。针对此问题,本文将主要提供一套H5多设备断点和响应式组件方案,实现页签栏响应式、网格响应式、类挪移布局等效果,提升H5一多适配的开发效率。首先介绍多设备断点能力,然后分别介绍组件的使用说明,结合组件效果提供对应场景的开发案例,最终提供示例代码指导实际开发。

以下是使用组件库的一些前置要求:

组件库安装

根据您使用的包管理器和web框架,请选择对应的安装命令。

Npm

// Vue2
npm install web-multidevice-advanced-vue2
// Vue3
npm install web-multidevice-advanced-vue3
// React
npm install web-multidevice-advanced-react

Yarn

// Vue2
yarn add web-multidevice-advanced-vue2
// Vue3
yarn add web-multidevice-advanced-vue3
// React
yarn add web-multidevice-advanced-react

多设备断点

断点设计原理

H5断点是基于鸿蒙多设备封装的一套断点机制,通过设置断点,让开发者可以结合窗口宽度去实现不同的页面布局效果。

断点以应用窗口宽度为基准,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,默认提供的断点区间如下所示。

断点名称取值范围(px)
xs[0, 320)
sm[320, 600)
md[600, 840)
lg[840, 1440)
xl[1440, +∞)

开发者可以根据实际需要调整默认的断点区间,也可以在xl后面新增xxl断点以适配更复杂的布局场景。

多设备适配指导

在具体开发过程中,使用breakpointManager需先从web-multidevice-advanced-ui/hookInput引用BreakpointManager这个类,然后创建BreakpointManager实例。建议创建BreakpointManager实例放在公共文件里,然后导出。这样的话,设备断点值变更时就只需要调整一个地方。使用breakpointManager时,需调用subscribeToBreakpoint方法订阅断点变化,然后在回调函数里使用useBreakpointValue方法来获取不同断点下属性的不同值。最后在页面销毁前需要取消断点订阅并销毁实例。

具体示例如下所示:

创建断点管理公共实例并导出

// utils/breakpointInit.ts
import { BreakpointManager } from "web-multidevice-advanced-ui/src/hookInput";

// 实例化断点管理类,断点区间支持自定义
export const breakpointManager = new BreakpointManager({
  xs: 0,
  sm: 320,
  md: 600,
  lg: 840,
  xl: 1440,
});

订阅断点变化并获取不同断点下的属性值

// xxx.vue
import { breakpointManager } from "./utils/breakpointInit";

const contentCols = ref("12");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint((bp) => {
  // bp:实时的断点值
  // 根据当前断点区间,返回对应的属性值
  contentCols.value = breakpointManager.useBreakpointValue({
    xs: "12",
    sm: "12",
    md: "6",
    lg: "6",
    xl: "6",
  });
  // 获取当前的断点值
  breakpointManager.getCurrentBreakpoint();
  // 获取当前的内置断点类型和区间
  breakpointManager.getBreakpoints();
});

页面销毁前取消断点订阅并销毁实例

// xxx.vue
onUnmounted(() => {
  // 取消订阅
  unsubscribe();
  // 销毁实例,移除监听器,清空回调函数集合
  breakpointManager.destroy();
});

页签栏响应式组件实现多设备适配

组件使用说明

H5页签栏响应式组件MultiTabBar,包含父组件TabBar和子组件TabBarItem两部分,主要用于实现内容视图切换的布局效果。组件结合多设备断点能力,能够适配不同类型的布局场景。

组件导入方式

  • Vue2
// xxx.vue
import { TabBarVue, TabBarItemVue } from "web-multidevice-advanced-vue2/src";
  • Vue3
// xxx.vue
import { TabBarVue, TabBarItemVue } from "web-multidevice-advanced-vue3/src";
  • React
// xxx.tsx
import { TabBarReact, TabBarItemReact } from "web-multidevice-advanced-react/dist/index";

默认布局效果

MultiTabBar组件基于默认断点区间,内置了一套多设备布局效果,如下图所示

TabBar父组件 API

  1. Attribute

  1. Event

TabBarItem子组件 API

推荐使用方式(以Vue3为例)

MultiTabBar组件在使用时建议封装为一个独立的组件,置于嵌套路由RouterView外层,以确保页面切换时组件不会刷新。同时,需要借助断点能力实现路由视图的页签栏避让。

// APP.vue
<template>
  <div>
    <TabBar />
    <div class="template-main">
      <router-view />
    </div>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
// 导入封装后的MultiTabBar组件
import TabBar from "./components/TabBar/index.vue";
// 导入断点公共实例
import { breakpointManager } from "./utils/breakpointInit";

const marginLeft = ref("0");
const marginBottom = ref("0");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  // 传入不同断点下的属性值,根据当前断点返回对应的值
  marginLeft.value = breakpointManager.useBreakpointValue({
    xs: 0,
    sm: 0,
    md: 0,
    lg: "96px",
    xl: "96px",
  });
  marginBottom.value = breakpointManager.useBreakpointValue({
    xs: "78px",
    sm: "78px",
    md: "78px",
    lg: 0,
    xl: 0,
  });
  // 设置CSS变量
  document.documentElement.style.setProperty("--marginLeft", marginLeft.value);
  document.documentElement.style.setProperty("--marginBottom", marginBottom.value);
});
onUnmounted(() => {
  // 取消订阅断点变化
  unsubscribe();
  // 销毁实例,移除监听器,清空回调函数集合
  breakpointManager.destroy();
});
</script>

<style lang="less" scoped>
.template-main {
  // 设置margin样式,实现页签栏避让
  margin-left: var(--marginLeft);
  margin-bottom: var(--marginBottom);
  overflow: scroll;
}
</style>

MultiTabBar组件封装示例代码如下所示:

// TabBar/index.vue
<template>
  <TabBarVue :onTabItemClick="tabItemClick"
             :vertical="vertical">
    <TabBarItemVue v-for="item in tabItems" 
                   :name="item.name" 
                   :key="item.key">
      <img :src="item.imgSrc" />
      <span>{{ item.name }}</span>
    </TabBarItemVue>
  </TabBarVue>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
import { TabBarVue, TabBarItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";

const vertical = ref(false);
const tabItems = ref([
  { key: "first", name: "首页", imgSrc: "../assets/tab_home_selected.svg", urlName: "/" },
  { key: "second", name: "分类", imgSrc: "../assets/tab_classification.svg", urlName: "/classify" },
  { key: "third", name: "发现", imgSrc: "../assets/tab_discovery.svg", urlName: "/discovery" },
]);
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  // 传入不同断点下的vertical属性值,根据当前断点返回对应的值
  vertical.value = breakpointManager.useBreakpointValue({
    xs: false,
    sm: false,
    md: false,
    lg: true, 
    xl: true,
  });
});
// 定义自定义事件
const tabItemClick = ({ index, name }) => {
  // 添加点击事件逻辑
};
onUnmounted(() => {
  // 取消订阅断点变化
  unsubscribe();
});
</script>

场景案例

实现效果

!注意:
实现效果中的红色框仅表示组件位置,不代表对应组件的边框。

关键代码(以vue3为例)

  • onTabItemClick为组件绑定的点击事件,可以在这里实现点击切换图片的效果已及点击进行路由跳转的功能。
  • vertical属性可以设置页签栏是否为纵向,true表示纵向左边tab,false为横向底部tab。(暂不支持其他位置的tab)。

更多属性及参数参考 页签栏组件MultiTabBar

代码示例如下:

// web_advanced_ui_sample\vue3_sample\src\App.vue
<template>
  <div>
    <TabBar />
    <div class="template-main">
      <router-view />
    </div> 
  </div>
</template>
// web_advanced_ui_sample\vue3_sample\src\components\TabBar\index.vue
<template>
  <TabBarVue :onTabItemClick="tabItemClick" 
             :vertical="vertical" 
             :selectedBgColor="selectedBgColor" 
             :bgColor="defaultBgColor"
             :selectedColor="selectedColor" 
             :width="barWidth"
             :layoutMode="layoutMode" >
    <TabBarItemVue v-for="item in tabItems" 
                   :name="item.key" 
                   :key="item.key">
      <img :src="item.imgSrc" />
      <span>{{ item.name }}</span>
    </TabBarItemVue>
  </TabBarVue>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
import { TabBarVue, TabBarItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";
import { useRouter } from "vue-router";

const router = useRouter();
// 导航栏文本图片信息
const tabItems = ref([
  { key: "first", name: "首页", imgSrc: "../assets/tab_home_selected.svg", urlName: "/" },
  { key: "second", name: "分类", imgSrc: "../assets/tab_classification.svg", urlName: "/classify" },
  { key: "third", name: "发现", imgSrc: "../assets/tab_discovery.svg", urlName: "/discovery" },
  { key: "forth", name: "购物袋", imgSrc: "../assets/tab_shopping_bag.svg", urlName: "/shoppingBag" },
  { key: "fivth", name: "我的", imgSrc: "../assets/tab_mine.svg", urlName: "/mine" },
]);
const vertical = ref(false);
const selectedBgColor = ref("#f1f3f5");
const defaultBgColor = ref("#f1f3f5");
const barWidth = ref("100%");
const selectedColor = ref("#d20a2c");
const layoutMode = ref("vertical");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  vertical.value = breakpointManager.useBreakpointValue({
    xs: false,
    sm: false,
    md: false,
    lg: true, 
    xl: true,
  });
  barWidth.value = breakpointManager.useBreakpointValue({
    xs: "100%",
    sm: "100%",
    md: "100%",
    lg: "98px",
    xl: "98px",
  });
});
// 页签栏点击事件:切换路由,更换icon图标
const tabItemClick = ({ index }) => {
  let item = tabItems.value[index];
  router.push(item.urlName);
  for (let i = 0; i < tabItems.value.length; i++) {
    let curItem = tabItems.value[i];
    if (curItem.imgSrc.endsWith("_selected.svg")) {
      curItem.imgSrc = curItem.imgSrc.replace("_selected.svg", ".svg");
    }
    if (index === i && !curItem.imgSrc.endsWith("_selected.svg")) {
      curItem.imgSrc = curItem.imgSrc.replace(".svg", "_selected.svg");
    }
  }
};
// 取消订阅,销毁断点实例
onUnmounted(() => {
  unsubscribe();
  breakpointManager.destroy();
});
</script>

<style lang="less" scoped>
@import './index.less';
</style>

网格布局响应式组件实现多设备适配

组件使用说明

H5网格布局响应式组件MultiGrid,包含父组件MultiGrid和子组件MultiGridItem两部分,主要用于提供精确的行和列控制,实现复杂的二维布局效果。组件结合多设备断点能力,能够适配不同类型的布局场景。

组件导入方式

  • Vue2
// xxx.vue
import { MultiGridVue, GridItemVue } from "web-multidevice-advanced-vue2/src";
  • Vue3
// xxx.vue
import { MultiGridVue, GridItemVue } from "web-multidevice-advanced-vue3/src";
  • React
// xxx.tsx
import { MultiGridReact, GridItemReact } from "web-multidevice-advanced-react/dist/index";

默认布局效果

MultiGrid组件基于默认断点区间,内置了一套多设备布局效果,如下图所示

MultiGrid父组件 API

  1. Attribute

  1. Event

MultiGridItem子组件 API

推荐使用方式(以Vue3为例)

// MultiGrid/index.vue
<template>
  <MultiGridVue :onGridItemClick="gridItemClick"
                :columnsTemplate="columnsTemplate">
    <GridItemVue v-for="item in gridItems" 
                 :name="item.name" 
                 :key="item.id">
      <img :src="item.imgSrc" />
      <span>{{ item.name }}</span>
    </GridItemVue>
  </MultiGridVue>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
import { MultiGridVue, GridItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";

const columnsTemplate = ref("1fr 1fr 1fr 1fr 1fr");
const gridItems = ref([
  { id: 1, name: "智慧办公", imgSrc: require("../../assets/categories_1.png") },
  { id: 2, name: "智慧家居", imgSrc: require("../../assets/categories_2.png") },
  { id: 3, name: "智能手机", imgSrc: require("../../assets/categories_3.png") },
]);
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  // 传入不同断点下的columnsTemplate属性值,根据当前断点返回对应的值
  columnsTemplate.value = breakpointManager.useBreakpointValue({
    xs: "1fr 1fr 1fr 1fr 1fr",
    sm: "1fr 1fr 1fr 1fr 1fr",
    md: "1fr 1fr 1fr 1fr 1fr 1fr 1fr",
    lg: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr", 
    xl: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr",
  });
});
// 定义自定义事件
const gridItemClick = ({ index, name }) => {
  // 添加点击事件逻辑
};
onUnmounted(() => {
  // 取消订阅断点变化
  unsubscribe();
  // 销毁实例,移除监听器,清空回调函数集合
  breakpointManager.destroy();
});
</script>

场景案例

实现效果

注意:
实现效果中的红色框仅表示组件位置,不代表对应组件的边框。

关键代码(以vue3为例)

  • 网格列数通过columnsTemplate设置,结合breakpointManager断点管理即可实现在不同设备上展示不同的列数。本示例中以fr关键字设置列数。
  • onGridItemClick为组件MultiGridVue自带的点击事件,在vue3中也可以在GridItemVue里添加@click点击事件,但是在vue2中建议使用onGridItemClick实现子组件的点击事件。-

其他设置方式及其他属性参考 网格组件multigrid。

具体示例如下:

// web_advanced_ui_sample\vue3_sample\src\components\Categories\index.vue
<template>
  <div class="categories">
    <MultiGridVue :columnsTemplate="columnsTemplate" 
                  gridRowGap="10px" 
                  gridColumnGap="10px"
                  onGridItemClick="pageJump">
      <GridItemVue class="categories-item"
                   v-for="item in categoriesItems" 
                   :name="item.key" 
                   :key="item.key">
        <img :src="item.imgSrc" />
        <span>{{ item.name }}</span>
      </GridItemVue>
    </MultiGridVue>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
import { MultiGridVue, GridItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";
import { useRouter } from "vue-router";

const router = useRouter();
// 网格项点击事件:路由跳转
const pageJump = () => {
  router.push("detail_pay");
};
// 网格组件元素
const categoriesItems = [
  { id: 1, name: "智慧办公", imgSrc: require("../../assets/categories_1.png") },
  { id: 2, name: "华为手机", imgSrc: require("../../assets/categories_2.png") },
  { id: 3, name: "运动健康", imgSrc: require("../../assets/categories_3.png") },
  { id: 4, name: "华为智选", imgSrc: require("../../assets/categories_4.png") },
  { id: 5, name: "企业商用", imgSrc: require("../../assets/categories_5.png") },
  { id: 6, name: "智慧家居", imgSrc: require("../../assets/categories_6.png") },
  { id: 7, name: "影音娱乐", imgSrc: require("../../assets/categories_7.png") },
  { id: 8, name: "AITO汽车", imgSrc: require("../../assets/categories_8.png") },
  { id: 9, name: "鸿蒙智联", imgSrc: require("../../assets/categories_9.png") },
  { id: 10, name: "全屋智能", imgSrc: require("../../assets/categories_10.png") },
];
const columnsTemplate = ref("1fr 1fr 1fr 1fr 1fr");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  columnsTemplate.value = breakpointManager.useBreakpointValue({
    xs: "1fr 1fr 1fr 1fr 1fr",
    sm: "1fr 1fr 1fr 1fr 1fr",
    md: "1fr 1fr 1fr 1fr 1fr 1fr 1fr",
    lg: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr",
    xl: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr",
  });
});
// 取消订阅,销毁断点实例
onUnmounted(() => {
  unsubscribe();
  breakpointManager.destroy();
});
</script>

<style lang="less" scoped>
@import './index.less';
</style>

类挪移布局响应式组件实现多设备适配

组件使用说明

H5类挪移布局响应式组件MultiDiversion,包含父组件MultiDiversion和子组件MultiDiversionItem两部分,主要用于根据布局的宽度选择是上下布局还是左右布局效果。组件结合多设备断点能力,能够适配不同类型的布局场景。

组件导入方式

  • Vue2
// xxx.vue
import { MultiDiversionVue, DiversionItemVue } from "web-multidevice-advanced-vue2/src";
  • Vue3
// xxx.vue
import { MultiDiversionVue, DiversionItemVue } from "web-multidevice-advanced-vue3/src";
  • React
// xxx.tsx
import { MultiDiversionReact, DiversionItemReact } from "web-multidevice-advanced-react/dist/index";

默认布局效果

MultiDiversion组件基于默认断点区间,内置了一套多设备布局效果,如下图所示

MultiDiversion父组件 API

  1. Attribute

MultiDiversionItem子组件 API

推荐使用方式(以Vue3为例)

// MultiDiversion/index.vue
<template>
  <MultiDiversionVue :direction="diversionDirection">
    <DiversionItemVue name="firstContent" >
      // 内容区1
    </DiversionItemVue>
    <DiversionItemVue name="secondContent" >
      // 内容区2
    </DiversionItemVue>
  </MultiDiversionVue>
</template>

<script setup>
import { ref, onUnmounted } from "vue";
import { MultiDiversionVue, DiversionItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";

const diversionDirection = ref("vertical");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  // 传入不同断点下的diversion属性值,根据当前断点返回对应的值
  diversionDirection.value = breakpointManager.useBreakpointValue({
    xs: "vertical",
    sm: "vertical",
    md: "horizontal",
    lg: "horizontal", 
    xl: "horizontal",
  });
});
onUnmounted(() => {
  // 取消订阅断点变化
  unsubscribe();
  // 销毁实例,移除监听器,清空回调函数集合
  breakpointManager.destroy();
});
</script>

场景案例

实现效果

注意:
实现效果中的红色框仅表示组件位置,不代表对应组件的边框。

关键代码(以vue3为例)

  • 子组件name属性必填,且一个项目中子组件name属性不能重复。
  • direction为类挪移组件里两个组件的排列方式,"horizontal":横向排列,"vertical":纵向排列。结合breakpointManager断点管理即可实现在不同设备上展示不同的布局方式。
  • diversionCols为类挪移子组件里两个组件所占的比例,类挪移组件默认宽度为12。子组件设置大于12时会占满整个类挪移组件的宽度。

更多属性及设置参考 类挪移布局组件multidiversion

具体代码如下:

// web_advanced_ui_sample\vue3_sample\src\components\PageHeader\index.vue
<template>
  <div class="home-header">
    <MultiDiversionVue :direction="multiDiversionDirection">
      <DiversionItemVue name="firstTitle" 
                        :diversionCols="tabBarCols" 
                        :diversionGap="barGap">
        <div class="top-tab-bar">
          <span class="top-tab-bar-text-selected">华为</span>
          <span class="top-tab-bar-text">AITO汽车</span>
          <span class="top-tab-bar-text">华为智选</span>
          <span class="top-tab-bar-text">生态周边</span>
        </div>
      </DiversionItemVue>
      <DiversionItemVue name="secondTitle" 
                        :diversionCols="searchBarCols" 
                        :diversionGap="barGap">
        <div class="search-bar">
          <input type="search-bar-text" 
                 class="search-bar-input" 
                 placeholder="搜索..." />
          <img class="search-bar-image" 
               src="../../assets/line_viewfinder.svg" />
          <img class="search-bar-image" 
               src="../../assets/msg_big.png" />
        </div>
      </DiversionItemVue>
    </MultiDiversionVue>
  </div>
</template>

<script setup>
import { onUnmounted, ref } from "vue";
import { MultiDiversionVue, DiversionItemVue } from "web-multidevice-advanced-vue3/src";
import { breakpointManager } from "../../utils/breakpointInit";

const multiDiversionDirection = ref("vertical");
const tabBarCols = ref("7");
const searchBarCols = ref("5");
const barGap = ref("10px");
// 订阅断点变化
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => {
  multiDiversionDirection.value = breakpointManager.useBreakpointValue({
    xs: "vertical",
    sm: "vertical",
    md: "horizontal",
    lg: "horizontal",
    xl: "horizontal",
  });
  tabBarCols.value = breakpointManager.useBreakpointValue({
    xs: "12",
    sm: "12",
    md: "7",
    lg: "8",
    xl: "8",
  });
  searchBarCols.value = breakpointManager.useBreakpointValue({
    xs: "12",
    sm: "12",
    md: "5",
    lg: "4",
    xl: "8",
  });
});
// 取消订阅,销毁断点实例
onUnmounted(() => {
  unsubscribe();
  breakpointManager.destroy();
})

</script>
<style lang="less" scoped>
@import './index.less';
</style>

示例代码

基于H5框架的多设备开发sample示例代码地址


HarmonyOS码上奇行
11.3k 声望4.1k 粉丝

欢迎关注 HarmonyOS 开发者社区:[链接]