H5:“同样是返回,我们不一样”

3,965 阅读2分钟
原文链接: elevenbeans.github.io

引言

有一些 H5 开发者耳熟能详的问题。

比如,老板会问你:“你这个 H5 页面为什么这么卡?为啥 IOS 那么流畅?”,

再比如,业务方会问你:“为什么这个功能你做不了?人家 Native 可以做?”,

再再比如,交互甚至也会经常挑战你:“为什么安卓能做到这样的交互效果,你不可以?”。

我只想说,作为 H5 开发,我们是有苦衷的 =,=

心痛的简直无法呼吸。

这个问题确实困扰了挺久

H5 页面中,浏览器/安卓 的 back(返回) 键,默认会直接回退到上一个页面,这听起来似乎十分正常。

但是对于一个以 Web App 为导向的产品来讲,当前习惯 APP 交互操作的用户的第一反应应该会是:如果当前页面有浮层的话,先解散浮层(不管你承不承认,事实如此)。

话不多说直接上图:

点击浏览器回退直接。。。 体验不够好!!!

说实话,关于这个交互的优化,一开始我是拒绝的。

浏览器默认行为!你要我怎样?我即便是改了,有很多副作用你知道吗?history 你能随随便便玩得起?

直到某天灵(kan)机(dao)一(jing)动(pin),有(bei)了(da)头(lian)绪(le)。

我觉得不做不行了

一样的套路,要把大象装冰箱,总共分几步?

第 1 步:浮层被点击唤起时,为 URL Push 一个 Hash 值(要唯一);
第 2 步:浮层被用户通过自定义关闭按钮解散时,主动去掉对应 Hash;
第 3 步:监听popstate事件,用户点击浏览器/安卓回退按钮时,解散对应浮层(通过唯一 Hash 值匹配),此时 Hash 值会被浏览器默认 pop 掉,这也是点击回退之后浏览器并不回到前一个页面的关键!!!因为正常的路由并没有变化!!!

Done~

现在看起来就像那么回事了:

当然,还有复杂一点的,嵌套浮层的 case 需要考虑。不过这时候我觉得已经触及我文字表达能力的盲区了 =,=

上代码:

这是我封装好的一个 React 组件 Enback 的代码:

// Enback component
import React from 'react';
import PropTypes from 'prop-types';
let hashStr = location.hash;
class Enback extends React.Component {
  static defaultProps = {
    uniqueKey: 'newPop'
  }
  static propTypes = {
    uniqueKey: PropTypes.string
  }
  constructor(props) {
    super(props);
  }
  componentDidMount = () => {
    const { uniqueKey, popStateCallback } = this.props;
    // why handle history twice? https://stackoverflow.com/questions/11092736/window-onpopstate-event-state-null
    if (location.hash) {
      history.replaceState({ 'uniqueKey': uniqueKey }, null, `${location.hash}`);
      history.pushState({ 'uniqueKey': uniqueKey }, null, `${location.hash}/${uniqueKey}`);
    } else {
      history.replaceState({ 'uniqueKey': uniqueKey }, null, `#`);
      history.pushState({ 'uniqueKey': uniqueKey }, null, `#${uniqueKey}`);
    }
    hashStr = location.hash;
    window.addEventListener('popstate', function(e) {
      if (e.state && e.state.uniqueKey === uniqueKey) {
        popStateCallback(uniqueKey);
        hashStr = location.hash;
      }
    });
  }
  truncate(arr) {
    var newArr = arr.join().split(',');
    newArr.pop();
    return newArr;
  }
  componentWillUnmount = () => {
    this.setState = () => {
      return;
    };
    const { uniqueKey } = this.props;
    if (hashStr.indexOf('/') !== -1) {
      history.replaceState(null, null, this.truncate(hashStr.split('/')));
    } else {
      if (uniqueKey === hashStr.split('').slice(1).join('')) {
        history.replaceState(null, null, '#');
      }
    }
    hashStr = location.hash;
  }
  render() {
    return (
      
{ React.Children.map( this.props.children, child =>
{ child }
) }
); } } export default Enback;

这是用法:

// Useage
import Enback from '../../enback';
export default () => (
   {/* here to hide your popup*/} }
  >
    
This is a popup
);

不玩 React?没关系,这里有原生的做法:github.com/zhaoqize/bl…

BTW, 副作用其实还是有的。你们猜猜是啥?

At last

谢谢捧场,欢迎 issue 讨论,随手点个 Star 更好 →_→ GitRepo