在混合式开发中,如果原生app不提供监听app回退事件的方法那么H5是不知道app已经回退了,但有时候我们又需要去监听页面的回退,并做相应处理。

比如,在商品支付页面,输入支付密码是一个类似微信支付的弹窗,用户打开这个弹窗后准备输入支付密码,但此时用户又不想支付了,此时用户就会触发app的返来关闭这个弹窗,如下视频:

如果H5不做监听并阻止的话那么用户的这个动作就变成了返回上一个页面了

1、实现原理

实现原理:监听history.state的变更,每次打开弹窗时向history.state添加一个新状态
实现步骤:

  1. 页面初始化后监听popstate事件,在该事件中进行业务处理

    let originState = history.state.current; // 存储原始的状态
    let onPopstate = function (e) {
      console.log('监听到页面发生动作', e.state, originState);
      if (e.state && (e.state.current === originState || e.state.target === originState)) {
     // 在这里可以进行业务处理,如关闭弹窗,提示用户未处理的事情等
      }
    };
    
    // 监听 popstate 事件
    window.addEventListener("popstate", onPopstate, false);

    注意:这里需要存储页面一进来时history.state的状态,当页面发生回退时才能知道在当前页面发生了回退

  2. 打开弹窗时向hostory.state添加一个新状态

    let changeHistoryState = function () {
      // 这里只需要再添加一个原始的状态即可,当发送回退时前面添加的`popstate`事件处理函数就能正确处理了
      window.history.pushState({target: originState, random: Math.random()}, "", location.href);
    };
  3. H5主动关闭弹窗
    h5主动关闭弹窗时需调用window.history.back()回退到上一个状态,否则用户想返回上一个页面时需要触发两次app的回退才能回到上一个页面

2、代码实现

useListenerAppBack.js

import {
  onUnmounted
} from 'vue';

/**
 * 监听原生app返回事件并阻止返回到上一个页面
 */
export function useListenerAppBack (onAppBack) {
  let appBack = function () {
    window.history.back();
  };
  let appForward = function () {
    window.history.forward();
  };
  let appGo = function (delta) {
    window.history.go(delta);
  };
  if (!window.history.pushState) {
    return {
      appBack,
      appForward,
      appGo,
      changeHistoryState () {
        console.warn('Browser does not support pushState!');
      }
    };
  }
  let originState = history.state.current; // 存储原始的状态
  // let statePushed = false;

  let changeHistoryState = function () {
    // 这里只需要再添加一个原始的状态即可
    window.history.pushState({target: originState, random: Math.random()}, "", location.href);
  };

  let onPopstate = function (e) {
    console.log('监听到页面发生动作', e.state, originState);
    if (e.state && (e.state.current === originState || e.state.target === originState)) {
      onAppBack(e.state);
    }
  };

  // 监听 popstate 事件
  window.addEventListener("popstate", onPopstate, false);

  onUnmounted(function () {
    console.log('移除popstate事件监听');
    // 移除监听 popstate 事件
    window.removeEventListener("popstate", onPopstate, false);
  });

  return {
    appBack,
    appForward,
    appGo,
    changeHistoryState
  };
};

业务代码中使用,pay.vue:

<template>
  <div class="pay">
    <bs-button @click="openPayDialog" :loading="paying" :disabled="paying">立即支付</bs-button>

    <!--支付弹窗-->
    <PayDialog
      v-model:visible="payDialogData.visible"
      @shadow-click="onPayDialogShadowClick"></PayDialog>
  </div>
</template>

<script>
import {
  ref,
  reactive
} from 'vue';
import { useListenerAppBack } from '@/hooks/useListenerAppBack';

export default {
  name: "Pay",
  setup () {
    let paying = ref(false);
    let payDialogData = reactive({
      visible: false
    });
    let { appBack, changeHistoryState } = useListenerAppBack(function () {
      if (payDialogData.visible) {
        // 如果支付弹窗已经打开,且页面发生了回退,则将支付弹窗关闭
        payDialogData.visible = false;
      }
    });

    let openPayDialog = function () {
      // 每次打开弹窗时都需改变一下history.state的状态
      changeHistoryState();
      payDialogData.visible = true;
    };

    // 支付弹窗的半透明遮罩点击事件,点击半透明遮罩后应该关闭支付弹窗,并回退一下history.state
    let onPayDialogShadowClick = function () {
      payDialogData.visible = false;
      appBack();
    };

    return {
      paying,
      payDialogData,

      openPayDialog,
      onPayDialogShadowClick
    };
  }
};
</script>

最终效果:
苹果手机效果
苹果手机效果

安卓手机效果
安卓手机效果

3、苹果手机中的坑

在苹果手机中支付弹窗不要添加侧滑效果,否则在回退关闭弹窗时会出现页面闪动一下的效果,如下图:
苹果手机中的坑

侧滑效果代码:

.pay-dialog{
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 1000;
  background-color: #fff;
  &.slide-enter-from,
  &.slide-leave-to{
    transform: translateX(100%);
  }
  &.slide-enter-active,
  &.slide-leave-active{
    transition: transform .2s linear;
  }
  &.slide-enter-to,
  &.slide-leave-from{
    transform: translateX(0);
  }
}

heath_learning
1.4k 声望31 粉丝