The background,
Inside the app, many pages need to be developed using H5. These H5 pages are rendered in the WebView container provided by the app. The app will provide two forms of Webview:
- Webviews with navigation: This type of WebView comes with its own top navigation. H5 developers can use jsBridge to dynamically configure navigation (title, right action, etc.), and H5 page related content will be rendered in the main area below the navigation.
- Immersive WebView: This type of Webview does not have navigation, and the entire page and the status bar at the top of the mobile device are all in H5. Usually, this type of Webview is used to meet some customized navigation scenarios (similar scenarios exist for small programs). At this point, H5 not only needs to develop the main part of the page, but also a navigation bar.
For the second kind of WebView, H5 development needs an immersive page container component, which contains navigation and page body, and the main body area supports beyond partial scrolling. This paper mainly provides a page container component and solutions to related problems.
2. Technology stack
The example in this paper adopts react framework development and hook component development
Page navigation
Before developing the container component, we first need to develop a generic navigation component.
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 color; * @param {object} style title theme configuration; * @param {object} Action button name; * @param {Boolean} bordered has a gray border on the bottom; * @param {function} goBack returns the button click event. */ 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);Copy the code
Here, we need to interact with the native through JsBridge to obtain the height of the system status bar for adaptive H5 navigation. At this point, we have developed our navigation bar, the specific style according to the company UI design, here do not paste style, eg
Page container components
Based on the developed navigation (which can be used directly if the company component library already has navigation components), we can then develop our page container:
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'; /** * page container, containing the header and the body of the page; @param {object} navigatorConf Navigation configuration information; * @param {any} children; * @param {Boolean} isEmpty whether the page isEmpty; * @param {Boolean} autoBgSize Whether the page background size is self-adaptive; * @param {any} emptyTips * @param {object} pageStyle style of the page body; * @param {object} bodyStyle The body of the page; * * eg: * pageStyle={{* backgroundColor: '#eceded', * backgroundImage: 'linear-gradient(68deg, #ff3c31 0%, #ff9a46 100%)', * backgroundRepeat: 'no-repeat', * }} * bodyStyle={{ background: / / seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded/seceded * When the gesture is pulled down, the top elastic scroll appears the same background color as the navigation background; * When the gesture is pulled up, the bottom elastic scroll appears the same background color as the page body; */ 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;Copy the code
The container is highly configurable, also solved the mobile end elastic rolling at the same time, the elasticity of the body area regional color of the scroll and navigation visual disconnect problem caused by different colors, before development, is the UI and not elastic when a drop-down intermediate in navigation bar background color, head down request, The color of the elastic area should be consistent with the navigation background. When you pull up, the color of the elastic area should be consistent with the main color of the page. Using autoBgSize, you can enable the color adaptation of the elastic scroll. Examples of specific effects:
Ok, so far, all the functions are completed, mainly according to the scrolling direction dynamic set backgroundSize, if you help, please click a like.