使用history.pushState和popstate的Ajax-当popstate状态属性为空时该怎么办?

22

我正在使用HTML5历史记录API与ajax加载内容进行尝试。

我有许多通过相对链接连接的测试页面。 我有这个JS,它处理那些链接上的点击。 当单击链接时,处理程序获取其href属性并将其传递给ajaxLoadPage(),该函数会将请求页面的内容加载到当前页面的内容区域中。(如果您以正常方式请求PHP页面,则设置为返回完整的HTML页面,但如果在请求URL后附加?fragment=true,则仅返回一部分内容。)

然后我的单击处理程序调用history.pushState()来显示地址栏中的URL并将其添加到浏览器历史记录中。

$(document).ready(function(){

    var content = $('#content');

    var ajaxLoadPage = function (url) {
        console.log('Loading ' + url + ' fragment');
        content.load(url + '?fragment=true');
    }

    // Handle click event of all links with href not starting with http, https or #
    $('a').not('[href^=http], [href^=https], [href^=#]').on('click', function(e){

        e.preventDefault();
        var href = $(this).attr('href');
        ajaxLoadPage(href);
        history.pushState({page:href}, null, href);

    });

    // This mostly works - only problem is when popstate happens and state is null
    // e.g. when we try to go back to the initial page we loaded normally
    $(window).bind('popstate', function(event){
        console.log('Popstate');
        var state = event.originalEvent.state;
        console.log(state);
        if (state !== null) {
            if (state.page !== undefined) {
                ajaxLoadPage(state.page);
            }
        }
    });

});

当您使用pushState将URL添加到历史记录中时,还需要包含popstate事件的事件处理程序以处理单击“后退”或“前进”按钮。(如果不这样做,单击“后退”会在地址栏中显示您推送到历史记录中的URL,但页面不会更新。)因此,我的popstate处理程序获取我创建的每个条目的状态属性中保存的URL,并将其传递给ajaxLoadPage以加载相应的内容。

对于由我的单击处理程序添加到历史记录中的页面,这种方法可以正常工作。但是,当我以“正常”方式请求它们时,浏览器添加到历史记录中的页面会发生什么情况呢?比如说我通过点击那个ajax加载的链接在第一个页面上着陆然后在我的网站上导航 - 如果我尝试通过历史记录回到那个第一个页面,则最后一个单击会显示第一个页面的URL,但不会在浏览器中加载该页面。为什么会这样呢?

我有点明白这与最后一个popstate事件的状态属性有关,该属性为null,因为只有由pushState()或replaceState()添加到历史记录中的条目才能给它赋值。但是,我第一次加载页面是以“正常”请求的方式进行的 - 为什么浏览器不能简单地后退并以正常方式加载初始URL呢?

6个回答

15

这是一个较旧的问题,但使用原生JavaScript解决此问题有一个更简单的答案。

对于初始状态,您不应该使用history.pushState,而应该使用history.replaceState

这两种方法的所有参数都相同,唯一的区别在于pushState创建了一个新的历史记录,因此它是问题的来源。replaceState仅替换该历史记录的状态,并将按预期方式运行,即返回到最初的起始页面。


1
这完全是我的问题的答案。当您首次访问页面并使用ajax加载它时,您需要使用此状态替换第一个状态。谢谢! - user1689571

6

我遇到了与原问题相同的问题。这一行

var initialPop = !popped && location.href == initialURL;

应该被更改为

var initialPop = !popped;

这已足以捕获初始弹出窗口,不需要将原始页面添加到pushState中。 即删除以下内容:
var home = 'index.html';
history.pushState({page:home}, null, home);

基于AJAX标签的最终代码(使用Mootools):
if ( this.supports_history_api() ) {
    var popped = ('state' in window.history && window.history.state !== null)
    ,   changeTabBack = false;

    window.addEvent('myShowTabEvent', function ( url ) {
       if ( url && !changingTabBack )
           setLocation(url);
       else
           changingTabBack = false;
       //Make sure you do not add to the pushState after clicking the back button
    });

   window.addEventListener("popstate", function(e) {
       var initialPop = !popped;
       popped = true;
       if ( initialPop )
           return;

       var tabLink = $$('a[href="' + location.pathname + '"][data-toggle*=tab]')[0];
       if ( tabLink ) {
           changingTabBack = true;
           tabLink.tab('show');
       }
   });
}

太好了!Erin,回答得很好。 - And Finally

5
我还是不太理解为什么后退按钮会出现这种情况,我本以为浏览器可以高兴地回到由正常请求创建的条目。也许当您使用pushState插入其他条目时,历史记录就不再按照正常方式运行了。但是我找到了一种使我的代码更好地运行的方法。您不能总是依赖状态属性包含您要返回的URL。但是通过历史记录向后步进会像您期望的那样更改地址栏中的URL,因此基于window.location加载内容可能更可靠。按照这个很好的例子,我已经改变了我的popstate处理程序,让它基于地址栏中的URL加载内容,而不是在状态属性中查找URL。
值得注意的是,某些浏览器(比如Chrome)在您最初访问某个页面时会触发一个popstate事件。当这种情况发生时,您可能会不必要地重新加载初始页面的内容。所以我从优秀的pjax中添加了一些代码来忽略这种初始弹出。
$(document).ready(function(){

    // Used to detect initial (useless) popstate.
    // If history.state exists, pushState() has created the current entry so we can
    // assume browser isn't going to fire initial popstate
    var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;

    var content = $('#content');

    var ajaxLoadPage = function (url) {

        console.log('Loading ' + url + ' fragment');
        content.load(url + '?fragment=true');

    }

    // Handle click event of all links with href not starting with http, https or #
    $('a').not('[href^=http], [href^=https], [href^=#]').on('click', function(e){

        e.preventDefault();
        var href = $(this).attr('href');
        ajaxLoadPage(href);
        history.pushState({page:href}, null, href);

    });

    $(window).bind('popstate', function(event){

        // Ignore inital popstate that some browsers fire on page load
        var initialPop = !popped && location.href == initialURL;
        popped = true;
        if (initialPop) return;

        console.log('Popstate');

        // By the time popstate has fired, location.pathname has been changed
        ajaxLoadPage(location.pathname);

    });

});

你可以对这个JS进行改进,只有在浏览器支持历史记录API时才附加单击事件处理程序。


它的功能很好,只有一个小问题,如果你点击页面重新加载,它只会加载页面的ajax部分。我想听听是否有人对页面重新加载有解决方案。 - Hydrino
@Hydrino 使用正确的URL推送状态,并使用AJAX加载部分页面。 - Praveen Kumar Purushothaman

1
今天我发现自己有类似的需求,发现你提供的代码非常有用。我遇到了与你相同的问题,我认为你所缺少的就是以同样的方式将索引文件或主页推送到历史记录中,就像你所有后续页面一样。
以下是我解决这个问题的示例(不确定是否是正确答案,但它很简单并且有效!):
var home = 'index.html';
history.pushState({page:home}, null, home);

希望这能有所帮助!

谢谢mattblac,我找到了另一种更灵活的方法,我会在这里发布它。 - And Finally

1
我知道这是一个旧问题,但是当尝试轻松管理状态时,采取以下方法可能更好:
$(window).on('popstate',function(e){
    var state = e.originalEvent.state;
    if(state != null){
        if(state.hasOwnProperty('window')){
            //callback on window
            window[state.window].call(window,state);
        }
    }
});

在这种方式下,您可以在将状态添加到历史记录时指定一个可选的回调函数,在触发 popstate 事件时,该函数会被调用,并将状态对象作为参数传递进去。
function pushState(title,url,callback)
{
    var state = {
        Url : url,
        Title : title,
    };
    if(window[callback] && typeof window[callback] === 'function')
    {
        state.callback = callback;
    }
    history.pushState(state,state.Title,state.Url);
}

你可以很容易地扩展它以适应你的需求。

0

最后说:

我本以为浏览器会很高兴地回到由正常请求创建的条目。

我在这里找到了对这种奇怪的浏览器行为的解释。解释是:

当您的站点第一次加载时,应保存状态,之后每次更改状态都要保存

我测试过了 - 它有效。

这意味着没有必要基于window.location来加载您的内容。

希望我没有误导您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接