AJAX内容和浏览器导航按钮

时间:2014-02-27 08:39:34

标签: javascript html ajax browser-history html5-history

您有以下jQuery事件:

listItems.on('click', function(ev) {
  ev.preventDefault();
  reload = true;
  var url = jQuery(this).attr('data-url');
  history.pushState({}, '', url);
  ...
}

通过AJAX加载动态内容并推送历史记录状态以修改浏览器中从www.domain.comwww.domain.com/page-x的网址。通过AJAX加载内容的唯一原因是我需要动画页面过渡。如果您转到www.domain.com/page-x,则会获得正常的HTML页面。

如果单击listItem用户后单击其浏览器上的后退按钮,则会出现此问题。该网址从www.domain.com/page-x更改为www.domain.com,但该网页未重新加载。这就是为什么我添加这个事件:

window.onpopstate = function() {
  if (reload == true) {
    reload = false;
    location.reload();
  }
}

如果当前页面是通过AJAX加载的,则在url更改后重新加载页面。它工作正常,但现在我有其他问题:

  1. 首页中的用户点击列表项;
  2. 浏览器网址更改,内容通过AJAX和动画加载;
  3. 用户点击BACK浏览器按钮;
  4. 浏览器网址更改为上一页并重新加载页面;
  5. 用户点击FORWARD浏览器按钮,网址更改但没有任何反应;
  6. 任何想法/解决方案都将受到高度赞赏。

1 个答案:

答案 0 :(得分:5)

您应该依赖reload=true,而不是使用event.state,这样当popstate出现时,您会获得您为该网址记录的内容的快照。

Manipulating the browser history (MDN)

例如:

listItems.on('click', function(ev) {
  ev.preventDefault();
  var url = jQuery(this).attr('data-url');
  history.pushState({ action: 'list-item-focused' }, '', url);
  ...
}

然后:

window.onpopstate = function(e) {
  /// this state object will have the action attribute 'list-item-focused'
  /// when the user navigates forward to the list item. Meaning you can
  /// do what you will at this point.
  console.log(e.state.action);
}

当用户回击时,你可能应该避免整页重新加载,而是回过头来回过去那里的内容,这样你就不会破坏页面。然后,您可以通过检查event.state.action并相应地对响应进行编码来说明目标网页与列表页面之间的区别。

window.onpopstate = function(e) {
  if ( e.state.action == 'list-item-focused') {
    /// a list item is focused by the history item
  }
  else {
    /// a list item isn't focused, so therefore landing page
    /// obviously this only remains true whilst you don't
    /// make further pushed states. If you do, you will have to
    /// extend your popstate to take those new actions into account.
  }
}

我确定你知道这一点,但pushState并非完全跨浏览器,所以如果不支持这些方法,你还应该预测用户会发生什么。


进一步改进

此外,当您使用jQuery时,它可以让您在状态对象中存储更多有用的信息,这可以帮助您在popstate中增强您的反应:

history.pushState({
  action: 'list-item-focused',
  listindex: jQuery(this).index()
});

您必须记住,您存储的任何数据都是序列化的,这意味着它很可能会转换为某种形式的非交互式字符串或二进制数据。这意味着您无法存储对元素或其他“实时”实例的引用;因此,事实上我正在存储列表项索引。

通过上述内容,您现在可以了解正在执行的操作以及可以在另一端使用以下内容检索的列表项:

listItems.eq(event.state.listindex)


怎么做onload?

MDN可以说明你应该做什么onload

  

阅读当前状态

     

当您的页面加载时,它可能具有非null状态对象。例如,如果页面设置了一个状态对象(使用 pushState() replaceState()),然后用户重新启动她的浏览器,就会发生这种情况。页面重新加载时,页面将收到 onload 事件,但没有 popstate 事件。但是,如果您读取history.state属性,则会返回在 popstate 被触发时您将获得的状态对象。

     

您可以使用 history.state 属性读取当前历史记录条目的状态,而无需等待 popstate 事件,如下所示:

简单地说,他们只是建议您在网页加载时收听当前history.state,并根据state.action描述的内容采取相应措施。这样,您就可以支持在页面的生命周期内触发的两个事件,即popstate,以及当用户直接跳转到导致页面加载的历史状态时。

因此,考虑到上述情况,最好按照这样的方式构建:

window.onpopstate = function(e) {
  reactToState(e.state);
}

window.onload = function(){
  history.state && reactToState(history.state);
}

var reactToState = function(state){
  if ( state.action == 'list-item-focused') {
    /// a list item is focused by the history item
  }
  else {
    /// a list item isn't focused, so therefore landing page
    /// obviously this only remains true whilst you don't
    /// make further pushed states. If you do, you will have to
    /// extend your popstate to take those new actions into account.
  }
}

为了简单起见,我使用了内联事件监听器,因为您的示例也是如此,但建议使用addEventListenerattachEvent(仅限IE)方法...或者更好,因为你正在使用jQuery。

jQuery(window)
  .on('popstate', function(e) {
    reactToState(e.originalEvent.state);
  }
  .on('load', function(){
    history.state && reactToState(history.state);
  }
;


切换状态

显然,为了在两个州之间移动,你需要知道这两个州是什么;这意味着有一些方法来记录您当前的状态 - 这样您就可以与新状态进行比较并采取相应的行动。由于你只有两种状态,这可能不是必要的,但我要求你始终考虑比现在更复杂的可能性。

我不知道你的代码的布局,因此它很难推荐你应该放置变量和其他类似项目的地方。但是,无论您如何存储信息,它都不会改变以下事实:如果您这样做,它将使您的生活和代码更容易:

var reactToState = function(state){
  var currentState = reactToState.currentState || {};
  if ( !currentState.action ) { currentState.action = 'start'; }
  if ( state.action == 'list-item-focused') {
    if ( currentState.action == 'start' ) {
      /// here we know we are shifting from the start page to list-item
    }
  }
  else {
    if ( currentState.action == 'list-item-focused' ) {
      /// here we know we are shifting from the list-item to the start
    }
  }
  /// as the state will be global for your application there is no harm
  /// in storing the current state on this function as a "static" attribute.
  reactToState.currentState = state;
}

更好的是,如果你不反对切换语句,你可以使上述内容更具可读性:

var reactToState = function(state){

  /// current state with fallback for initial state
  var currentState = reactToState.currentState || {action: 'start'};

  /// current to new with fallback for empty action i.e. initial state
  var a = (currentState.action||'start');
  var b = (state.action||'start');

  switch ( a + ' >> ' + b ) {
    case 'start >> list-item-focused':
      /// animate and update here
    break;
    case 'list-item-focused >> start':
      /// animate and update here
    break;
  }

  /// remember to store the current state again
  reactToState.currentState = state;
}