概述
应用使用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
- Attribute
- 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
- Attribute
- 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
- 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示例代码地址
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。