在app内部,不少页面须要利用H5开发,这些H5渲染在app提供的webview容器里,app会提供两种形式的webview:react
针对第二种webview,H5开发须要一个沉浸式的页面容器组件,该组件包含导航和页面主体,主体区域支持超出部分滚动,本文主要提供一种页面容器的组件以及相关问题的解决方案。web
本文示例采用react框架开发,使用hook开发组件小程序
在开发容器组件前,咱们首先要开发一个通用的导航组件。markdown
import React, { memo, useState, useCallback, useLayoutEffect } from 'react';import PropTypes from 'prop-types';import classnames from 'classnames';
import { useHistory } from 'react-router-dom';import { bridge } from 'jsbridge';
import WhiteLeftArrow from './icons/icon_arrow_left_white.png';
import BlackLeftArrow from './icons/icon_arrow_left_black.png';
import './style.less';
/**
* 沉浸式页面标题栏
* @param {string} title 页面标题;
* @param {string} theme 主题颜色;
* @param {object} style 标题的主题配置;
* @param {object} action 标题右侧的action按钮名称;
* @param {boolean} bordered 是否带底部灰色边框;
* @param {function} goBack 返回按钮的点击事件(不传默认返回上一层页面);
*/
function Navigator(props) {
const history = useHistory();
const barHeight = Number(
window.localStorage.getItem('STATUS_BAR_HEIGHT')
);
const { title, theme, style, action, bordered, goBack } = props;
const initHeight = barHeight && barHeight > 0 ? barHeight : 20;
const [systemStatusBarHeight, setSystemStatusBarHeight] = useState( initHeight );
const { name, onClick } = action;
useLayoutEffect(() => {
if (!barHeight) {
bridge.nativeGetDeviceInfo().then((res) => {
const { pixelDensity, statusBarHeight } = res;
const height = statusBarHeight / pixelDensity;
setSystemStatusBarHeight(height);
window.localStorage.setItem('STATUS_BAR_HEIGHT', height);
});
}
}, [barHeight]);
const goToPrevPage = useCallback(() => {
if (!goBack) {
history.go(-1);
return;
}
goBack();
}, [goBack, history]);
return (
<div
className={classnames('navigator--global-component', { bordered })}
style={{ paddingTop: systemStatusBarHeight, ...style }}
>
<div className="navigator__inner-wrapper">
<div className="left-icon" onClick={goToPrevPage}>
<img
alt="left arrow"
src={theme === 'white' ? BlackLeftArrow : WhiteLeftArrow}
/>
</div>
<div className="page-title">{title}</div>
{action && (
<div className="right-action" onClick={onClick}>
{name}
</div>
)}
</div>
</div>
);
}
Navigator.propTypes = {
title: PropTypes.string,
theme: PropTypes.string,
style: PropTypes.object,
bordered: PropTypes.bool,
action: PropTypes.object,
goBack: PropTypes.func,
};
Navigator.defaultProps = {
title: '',
theme: 'white',
style: {},
bordered: false,
action: {
name: '',
onClick: () => {},
},
goBack: null,
};
export default memo(Navigator);
复制代码
此处须要经过jsbridge与原生交互,来获取系统状态栏的高度作H5导航的自适应
。至此,咱们就开发好了咱们的导航栏,具体样式根据公司UI设计来,此处不贴样式了,egreact-router
基于开发好的导航(若公司组件库已有导航组件也可直接使用),咱们以后能够开发咱们的页面容器:app
import React, { useRef, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import Navigator from '@/components/navigator';
import EmptyData from '@/components/empty-data';
import './style.less';
/**
* 页面容器,包含头部和页面主体部分;
* @param {object} navigatorConf 导航配置信息;
* @param {any} children 页面内容;
* @param {boolean} isEmpty 是否空页面;
* @param {boolean} autoBgSize 页面背景大小是否自适应;
* @param {any} emptyTips 空页面时的提示文案;
* @param {object} pageStyle 页面主体的样式;
* @param {object} bodyStyle 页面主体的样式;
*
* 备注:
* 此处可实现H5弹性滚动下拉时背景色与头部保持一直的功能,具体实现以下:
*
* eg:
* pageStyle={{
* backgroundColor: '#eceded',
* backgroundImage: 'linear-gradient(68deg, #ff3c31 0%, #ff9a46 100%)',
* backgroundRepeat: 'no-repeat',
* }}
* bodyStyle={{ background: '#eceded' }}
* 设置pageStyle的背景色与页面主体的背景色一致,设置pageStyle的背景与导航一致,开启autoBgSize,
* 开启后,组件会根据页面滚动方向,动态的设置背景的大小,实现:
* 手势往下拉时,顶部弹性滚动出现与导航背景相同的背景色;
* 手势往上拉时,底部弹性滚动出现与页面主体相同的背景色;
*/
function PageContainer(props) {
const pageRef = useRef();
const {
navigatorConf,
children,
isEmpty,
emptyTips,
pageStyle,
autoBgSize,
bodyStyle,
} = props;
const [style, setStyle] = useState(pageStyle);
const [scrollDirection, setScrollDirection] = useState('down');
useEffect(() => {
const handlePageScroll = (e) => {
const { scrollTop } = e.target;
if (scrollTop > 0) {
setScrollDirection('up');
} else {
setScrollDirection('down');
}
};
const pageBody = document.querySelector('.page-container__body');
if (autoBgSize) {
pageBody.addEventListener('scroll', handlePageScroll, false);
}
return () => {
autoBgSize &&
pageBody.removeEventListener('scroll', handlePageScroll, false);
};
}, [autoBgSize]);
useEffect(() => {
if (autoBgSize) {
if (scrollDirection === 'down') {
setStyle({
...pageStyle,
backgroundSize: '100%',
});
} else {
setStyle({
...pageStyle,
backgroundSize: '0',
});
}
}
}, [autoBgSize, scrollDirection, pageStyle]);
return (
<div className="page-container--layout">
<Navigator {...navigatorConf} />
<div className="page-container__body" style={style} ref={pageRef}>
<div className="page-container__main" style={bodyStyle}>
{isEmpty ? <EmptyData tips={emptyTips} /> : children}
</div>
</div>
</div>
);
}
PageContainer.propTypes = {
navigatorConf: PropTypes.objectOf(PropTypes.any),
children: PropTypes.any,
isEmpty: PropTypes.bool,
autoBgSize: PropTypes.bool,
emptyTips: PropTypes.string,
pageStyle: PropTypes.objectOf(PropTypes.any),
bodyStyle: PropTypes.objectOf(PropTypes.any),
};
PageContainer.defaultProps = {
navigatorConf: {},
children: null,
isEmpty: false,
autoBgSize: false,
emptyTips: '',
pageStyle: {},
bodyStyle: {},
};
export default PageContainer;
复制代码
该容器高度可配置化,同时也解决了移动端弹性滚动时,主体区域的弹性滚动显示的区域颜色与导航栏颜色不一样致使的视觉脱节问题,以前开发时,就有UI提出不能弹性下拉时候中间出现与头部导航栏背景色不一样的颜色,要求往下拉的时候,弹性区域颜色保持和导航背景一致,往上拉的时候,弹性区域的颜色保持与页面主体颜色一致,经过autoBgSize的配置,能够开启该项弹性滚动的颜色适配,固然若是主体区域颜色自己和导航颜色一致或者不明显脱节,可不用作适配。具体效果示例:框架
好啦,至此,全部的功能都以完成,主要是根据滚动方向动态设置backgroundSize,若是对你有帮助,请点个赞。less