浏览器内popState监听器使用

浏览器内popState监听器使用

在前端开发过程中,在一些业务场景中可能会遇到监听路由前进后退、控制路由等情况。我们可以使用Web API提供的popState事件来处理这些情况,提到popState,应用中,通常pushState配合使用。

popState

usage

window.addEventListener('popstate', (event) => {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
});
 
window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。 需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法) 不同的浏览器在加载页面时处理popstate事件的形式存在差异。页面加载时Chrome和Safari通常会触发(emit )popstate事件,但Firefox则不会。

pushState

history.pushState(state, title[, url])

参数

state

状态对象是一个JavaScript对象,它与由创建的新历史记录条目相关联 pushState()。每当用户导航到新状态时,[popstate](https://developer.mozilla.org/en-US/docs/Web/Events/popstate)就会触发一个事件,并且state 该事件的 属性包含历史记录条目的状态对象的副本。

状态对象可以是任何可以序列化的对象。因为Firefox将状态对象保存到用户的磁盘上,以便用户重新启动浏览器后可以将其还原,所以我们对状态对象的序列化表示施加了640k个字符的大小限制。如果将序列化表示形式大于此值的状态对象传递给 pushState(),则该方法将引发异常。如果您需要更多空间,建议您使用[sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)和/或[localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)

title

当前大多数浏览器都忽略此参数,尽管将来可能会使用它。在此处传递空字符串应该可以防止将来对方法的更改。或者,您可以为要移动的州传递简短的标题。

url 可选的

新历史记录条目的URL由此参数指定。请注意,浏览器不会在调用之后尝试加载该URL pushState(),但是可能会稍后尝试加载该URL,例如,在用户重新启动浏览器之后。新的URL不必是绝对的。如果是相对的,则相对于当前URL进行解析。新网址必须与当前网址具有相同的来源;否则, pushState() 将引发异常。如果未指定此参数,则将其设置为文档的当前URL。

pushState类似于插入了一条新的历史记录,当用户返回时,只会返回到插入之前的url,再次点击返回才会回到原先的页面。调用次数和返回次数相关。popState用来监听历史记录变化。通过调用pushState方法,可以实现阻止浏览器立即返回上一页、自定义返回事件等。

下面是几种遇到的场景,欢迎大家补充!

  1. 用户触发后退时,阻止浏览器立即返回。

    const state = {
    	title: 'title',
    	url: location.hash,
    };
     
    window.history.pushState(state, 'detail', location.hash);
  2. 当页面中出现弹窗、全屏组件等情况,尤其是在移动端h5页面,当用户需要关闭弹窗、取消全屏组件时,可能会使用物理返回键,这时会直接返回上一页面,而不是关闭弹窗。

    // 在React框架中的示例
    componentDidMount() {
      const state = {
        title: 'title',
        url: location.hash,
      };
     
      if (!window.history.state) {
        window.history.pushState(state, 'detail', location.hash);
      }
      window.addEventListener('popstate', this.popStateHandler, false);
    }
     
    componentWillUnmount() {
      console.log('componentWillUnmount');
      window.removeEventListener('popstate', this.popStateHandler, false);
    }
     
    popStateHandler = (state) => {
      // 有弹窗时返回事件处理
      console.log(state);
      const { showModal } = this.state;
      const { currentTarget = {} } = state;
      const { location: { hash } } = currentTarget;
      if (showModal) {
        this.setState({
          showModal: false, // 关闭弹窗
        });
        const s = {
          title: 'title',
          url: location.hash,
        };
    		// 因为可能会出现多次弹窗,所以再次pushState
        window.history.pushState(s, 'detail', location.hash);
      } else if (hash.indexOf('detail') !== -1) {
        // 无弹窗时直接返回上一页面,调用一次goBack();
        // 这里通过hash对比currentTarget是否是当前页面,不进行判断直接返回会造成返回两次!!!
    		history.goBack();
    	}
    }
  3. 单独维护当前页面,当页面可能跳转至下一页面时,返回该页面时不再调用pushState

    const state = {
    	title: 'title',
    	url: location.hash,
    };
     
    if (!window.history.state) {
      // 做一个是否已经pushState的判断,没有pushState才调用,history.state即pushState的第一个参数
      window.history.pushState(state, 'detail', location.hash);
    }