百亿补贴通用H5导航栏方案
Tech导读
在移动端页面中,由于屏幕空间有限,导航条扮演着非常重要的角色,提供了快速导航到不同页面或功能的方式。用户也通常会在导航条中寻找他们感兴趣的内容,因此导航条的曝光率较高。在这样的背景下,提供一个动态灵活的导航条,为产品赋能,变得尤其重要。
导读
在移动端页面中,由于屏幕空间有限,导航条扮演着非常重要的角色,提供了快速导航到不同页面或功能的方式。用户也通常会在导航条中寻找他们感兴趣的内容,因此导航条的曝光率较高。在这样的背景下,提供一个动态灵活的导航条,为产品赋能,变得尤其重要。01 使用原生导航栏现状
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
拿iOS原生导航条为例,导航条作为页面进出栈的根视图连接器,以及生命周期的管理器。尤其是在作为webView Controller的父容器的时候,面对webview中h5页面灵活的的路由属性,以及一些难料的异常情况,原生很难也不便于频繁操作根试图容器,因此也产生了一些性能差、体验差、开发成本高、测试场景难覆盖等问题。安卓也有类似情况。
1.1 性能问题
ssr
预渲染时,无法对原生导航条进行预加载。对于百亿,便宜包邮等使用ssr预渲染的频道,因为原生导航栏无法进行预加载,导致上屏较慢等问题。
1.2 开发/测试成本高
原生导航条生命周期耦合。原生导航条作为webviewController的根容器,一旦操作时机不当,很可能影响到线上页面,而且最大的问题在于这种场景测试很难覆盖。比如: window.href.url
使用这种方式更新当前页面时,由于不同频道操作同一根导航条,会引发不可预知的问题;场景有限。站外场景无法使用原生导航条,一些业务方往往需要单独处理站内外,造成开发资源浪费。
1.3 体验差
webview初始化时会预置一个默认的导航条,然后根据前端配置,再去设置导航条的不同样式,无法避免的存在一个过渡期,体验较差。 window.location.reload()
刷新当前页面的时候,即便是在js中隐藏了导航条,webview
为了兼容一个线上问题,执行reload时此时会先展示原生导航条,直到执行了js的隐藏逻辑,才会被隐藏,体验较差。
1.4 难扩展造成营销资源浪费
无法扩展交互动效。得益于移动端页面中,导航条得天独厚的位置,产品往往希望有更生动的交互性,来提高曝光、粘性、活动触达率等。比如导航栏上挂载搜索框、以及吸顶、延伸动画、沉浸式、炫酷的营销icon等等。遗憾的是原生系统导航条不能全部支持,其实无论从视图层级上来说,还是从导航条职责上来说, apple
并不希望过多操作导航栏上的元素。也就造成了高曝光位置的资源浪费。
1.5 依赖性强
因为要依赖原生JS桥,就一定会存在版本限制问题。造成需求迭代慢,甚至随着时间的推移,版本卡口原因无迹可寻,代码调整战战兢兢,版本审核慢、周期长等问题。
02
解决方案
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
基于原生导航条现状,百亿补贴频道沉淀出了通用H5导航条组件@pango/navigation-bar
,具有以下优势:
1、性能好
支持ssr预渲染,上屏较快。
2、开发/测试成本低
人力节省百分之90%以上,以plus 95折为例,对接只需0.5/人日。
无场景限制。可用于站内外,ssr以及csr场景,无需站内外多次开发。
可配置。
@pango/navigation-bar
使用config的形式配置item,这么做的好处是一旦业务需求改动,只需调整配置,无需调整组件逻辑,极大降低开发和测试成本。另外如果你使用主站的webview并且配置了config,那么只需要简单的改动config,代码迁移成本低。该组件发布在JNPM上(https://npm.m.jd.com/package/@pango/navigation-bar),大小只有4.1K,接入简单。
导航条在频道内和其他普通楼层无异,生命周期隔离清晰,不会影响别的页面,测试成本低。
单向数据流设计,外部数据变化,组件UI及时响应,不存在原生的操作窗口问题,开发体验佳。
3、用户体验好
生命周期和其他楼层保持同步,规避了原生容器和H5页面天然的生命周期无法同步的问题,也就不存在两者之间的过渡问题,体验佳。
4、灵活定制
采用左、中、右、状态栏、导航栏分层设计的模式,支持传入React.ReactElement,比原生定制性更强,可灵活定制目前站内绝大部分导航条样式以及交互动画,合理高效利用导航条资源。
5、机型、系统兼容性好
参考原生导航栏异形屏适配方案,参考原生绝对布局思路,完美适配折叠屏、异形屏。
iOS9 - 最新 、Android5 - 最新均兼容性良好,未发现线上兼容异常。
6、不对外依赖
纯手工打造,未使用第三方库,不会对宿主造成依赖冲突,随时改动随时发布不存在版本控制,最大程度的降低和隔断对原生容器的版本依赖。
03
异常处理
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕
原生导航条作为根试图容器,容器内子视图异常不会影响根试图的展示,所以不用特殊处理html下载失败,js执行异常,服务挂掉等异常情况。但是H5导航条遇到这些异常情况,也要保证用户可以点击返回按钮返回上一页。
3.1 百补方案
异常场景1:业务js执行异常。
@pango/navigation-bar组件使用a标签渲染返回按钮,保证js执行异常时依然展示返回按钮,并且能正常响应返回事件。
业务展示兜底错误页时,会使用导航条兜底数据渲染导航条确保可返回上一级。
异常场景2:webview加载html失败。
qurey
参数hideNavi=1
,原生webview会通过该字段在webview出现之前隐藏导航条。但是因此也引发了一个风险:html加载失败时,会造成无头的问题。因此需要webview配合改造,一旦监测到html加载失败,原生webview要展示原生导航条。异常场景3:通天塔服务异常。
hideNavi
字段添加返回按钮或者通知webview展示默认导航条。3.2 竞品/兄弟频道相关现状
观察多个竞品以及兄弟频道,发现在上述的异常场景2、3下,均未做特别处理,展示无头错误页。如果此时原生禁用了右滑返回手势,页面将无法返回上一级,这无异是一个非常严重的缺陷(事实上有些竞品页面以及我们某些频道确实无法返回上一级)。
图1、2、3.
04 线上项目
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
05 线上成果展示
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
图4、5、6、7、8.
06 设计思路
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目参考原生
navigationBar
的设计思路,把整个导航栏分为左、右、中
三个区域,左、右区域根据内容自适应宽度,剩余空间为中间区域。左右区域接受items数组,可根据item接口协议设置左右的items,协议可自定义图片、尺寸、事件、间距、下拉菜单、是否动画响应等,已默认包含了关注、返回、更多、频道logo等常用元素,当然如有需要也可以自定义一个React.ReactElement
。中间区域只接受React.ReactElement
,你可以自由定制元素,传入navigation-bar
即可,一张图片一段文字,或者是一个搜索框……不管是伸缩或者是上滑吸顶,都可自定义。图9.
07
@pango/navigation-bar
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
该组件发布在JNPM上(https://npm.m.jd.com/package/@pango/navigation-bar),使用react + ts开发,大小只有4.1K。
文件结构:
图10.
08
使用方式
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
8.1 安装
npm i @pango/navigation-bar --registry=http://registry.m.jd.com
8.2 配置
你可以自由配置items除了"follow", "more","back","logo"
,这些已知的元素外还可以设置type:"common",
是一个通用类型的item;
scrollCallBack
会回调上滑比例,可根据该比例做交互动画。
import {
BACK_ICON,
FEEDBACK_ICON,
FEEDBACK_URL,
INavigationParams,
MORE_ICON,
RULE_ICON,
SHARE_ICON,
} from "@pango/navigation-bar";
setH5NavigationButton = (headerData) => {
const extend = headerData?.navigationBar?.extend;
const followInfo = headerData?.navigationBar?.followInfo;
const follow = {
type: "follow",
collectionId: String(followInfo?.themeId),
gapWidth: 12,
width: 55,
height: 22,
};
const moreItem = {
type: "more",
menuBackgroundColor: "white",
img: MORE_ICON,
title: "更多",
menuList: [],
};
moreItem.menuList.push({
icon: RULE_ICON,
title: "规则页",
menuEventData: extend?.guideUrl,
});
moreItem.menuList.push({
icon: SHARE_ICON,
title: "分享",
type: "share",
menuEventData: extend?.share,
});
const backItem = {
type: "back",
img: BACK_ICON,
canClick: !margicWindow,
title: "返回",
};
const backLogo = {
type: "logo",
img: DEFAULT_LOGO,
isAnimation: true,
gapWidth: 5,
width: 176,
height: 34
};
const navBarParams: INavigationParams = {
leftItems: [],
rightItems: [],
backgroundColor: "#FD4D00",
navHeight: this.status.navHeight,
};
navBarParams.leftItems.push(backItem, backLogo);
navBarParams.rightItems.push(moreItem, follow);
navBarParams.titleImgItem = TitleSearch({});
navBarParams.scrollCallBack = (scale) => {
this.setStatus({
navigationBarParams: Object.assign(this.status.navigationBarParams, {
titleImgItem: TitleSearch({ isCollapse: scale === 1 })
})
});
}
return navBarParams;
};
8.3 titlelmgltem
特别注意titleImgItem,这个属性是导航条中间区域的展示内容,
TitleSearch
是百亿补贴的搜索框,你可以参考该元素自定义中间区域。8.4 挂载
import { INavigationParams, NavigationBar } from "@pango/navigation-bar";
import "@pango/navigation-bar/lib/navigation-bar.scss";
css
.nav-bar {
width: 750px;
z-index: 1;
top: 0px;
}
<NavigationBar
className="nav-bar"
params={navBarParams} //!!!参考上一步的配置,自定义导航条参数
barHeight={200} //!!!自定义导航栏高度
event={do somethings}
/>
09
遇到了哪些问题
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目Q:若原生导航条隐藏,此时异常怎么办?异常分为以下3类:
异常场景1:业务js执行异常。
@pango/navigation-bar组件使用a标签渲染返回按钮,保证js执行异常时依然展示该标签,并且能正常相应出栈事件。
业务展示兜底错误页时,会使用导航条兜底数据渲染导航条。
异常场景2:webview加载html失败。
为了消除上面提到的过渡问题,业务链接中新增了qurey
参数hideNavi=1
,原生webview会通过该字段在webview出现之前隐藏导航条。但是因此也引发了一个风险:html加载失败时,会造成无头的问题。因此需要webview配合改造,一旦监测到html加载失败,原生webview要展示原生导航条。异常场景3:通天塔服务异常。
同样是场景2中的问题,需要通天塔配合改造通天塔服务异常的场景:依据链接中hideNavi
字段添加返回按钮或者通知webview展示默认导航条。若发现其他异常,麻烦提醒。Q:折叠屏怎么适配?折叠屏适配一直是前端适配的噩梦,噩梦的根本原因在于:宽度于高度的比例非常值,前端布局是往往会把px转换成vw,因此造成了异形屏适配难的问题。参考原生系统导航栏的绝对布局方案:@pango/navigation-bar
把导航条拆分为状态栏和导航栏上下两部分, 导航条宽度屏幕自适应,导航条高度跟随设备变化,并采用大写的PX
单位来固定元素尺寸。根据协议item宽高、间距仍可自定义,但是大写的PX
保证了item不会随着屏幕宽度而异常变化。
navigation-bar {
width: 750px; // 会转换成vw
height: 44PX; // 不会转换成vw
display: flex;
position: absolute;
.left-items-bg {
margin-left: 16PX; // 不会转换成vw
height: 22PX;
margin-top: 11PX;
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
}
}
Q:原生导航条优化?现状中的几个异常场景,仍需要webview配合一起整改,所以目前整改方案为:
业务链接中新增qurey
参数hideNavi=1
,此时 webview通过该字段在webview 出现之前隐藏导航条。由webview负责整改,跟版12.1.4。10
开源计划
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目经安全部门审核之后,会向外开源。
11 结语
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
导航条在移动端页面中的重要性无需多言,最终的目的是面向全集团,和通天塔以及hybrid团队,一起打造一根规范通用的H5导航栏,如果你在使用过程中发现一些现在没有考虑到的异常场景或者设计规范,请与作者联系,欢迎共同完善。 目前该组件下拉刷新还是要依赖原生的下拉刷新事件,后期会定制H5自己的下拉刷新。 一个规范的UI组件应该是一个有严格UI设计规范的,比如间距,字体大小、图片规范等。但是一期的设计中为了灵活,通过协议把UI把控留给了用户,也希望后面的迭代开发中融入更多规范的设计语言。
解放双手!ChatGPT助力编写JAVA框架
JDK8升级JDK11最全实践干货来了
618技术揭秘:大促弹窗搭投实践
求分享
求点赞
求在看