App內嵌H5沉浸式頁面容器組件如何解決彈性滾動的背景問題

1、背景

    在app內部,不少頁面須要利用H5開發,這些H5渲染在app提供的webview容器裏,app會提供兩種形式的webview:react

  1. 帶導航的webview:
    該類webview會自帶頂部導航,H5開發人員只需利用jsBridge動態配置導航(如標題、右側action等),H5頁面相關的內容會渲染在導航之下的主體區域。
  2. 沉浸式webview:
    該類webview不帶導航,整個頁面連同手機設備頂部的狀態欄都是H5內,一般該類webview是爲知足某些定製化的導航場景(小程序也有相似的場景),此時,H5不只須要開發頁面主體部分,同時也要開發一個導航欄。

針對第二種webview,H5開發須要一個沉浸式的頁面容器組件,該組件包含導航和頁面主體,主體區域支持超出部分滾動,本文主要提供一種頁面容器的組件以及相關問題的解決方案。web

2、技術棧

本文示例採用react框架開發,使用hook開發組件小程序

3、頁面導航

在開發容器組件前,咱們首先要開發一個通用的導航組件。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

4、頁面容器組件

基於開發好的導航(若公司組件庫已有導航組件也可直接使用),咱們以後能夠開發咱們的頁面容器: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

相關文章
相關標籤/搜索