22

我正在尝试使用 ajax 加载内容的 HTML5 历史 API。

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

然后我的点击处理程序调用 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 处理程序获取保存在我创建的每个条目的 state 属性中的 URL,并将其传递给 ajaxLoadPage 以加载适当的内容。

这适用于我的点击处理程序添加到历史记录的页面。但是当我“正常”请求浏览器添加到历史记录的页面时会发生什么?假设我正常登陆我的第一页,然后通过执行 ajax 加载的点击浏览我的网站 - 如果我然后尝试通过历史返回到该第一页,最后一次点击显示第一页的 URL,但没有'不在浏览器中加载页面。这是为什么?

我可以看出这与最后一个 popstate 事件的 state 属性有关。该事件的 state 属性为 null,因为只有通过 pushState() 或 replaceState() 添加到历史记录的条目才能为其赋值。但是我第一次加载页面是一个“正常”请求——为什么浏览器不只是退后一步并正常加载初始 URL?

4

6 回答 6

15

这是一个较老的问题,但是对于这个问题,使用本机 javascript 有一个更简单的答案。

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

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

于 2014-05-21T20:19:46.457 回答
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');
       }
   });
}
于 2013-11-29T12:37:58.207 回答
5

我仍然不明白为什么后退按钮会这样 - 我原以为浏览器会很乐意退回到由正常请求创建的条目。也许当您使用 pushState 插入其他条目时,历史记录会停止以正常方式运行。但我找到了一种让我的代码更好地工作的方法。您不能总是依赖于包含要退回的 URL 的 state 属性。但是回溯历史记录会改变地址栏中的 URL,因此根据 window.location 加载内容可能更可靠。按照这个很好的示例,我更改了我的 popstate 处理程序,以便它根据地址栏中的 URL 加载内容,而不是在 state 属性中查找 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);

    });

});

如果浏览器支持历史 API,您可以对此 JS 进行的一项改进是仅附加单击事件处理程序。

于 2013-01-09T12:15:49.757 回答
1

实际上,我今天发现自己也有类似的需求,并且发现您提供的代码非常有用。我遇到了与您相同的问题,并且我相信您所缺少的只是将索引文件或主页以与所有后续页面相同的方式推送到历史记录。

这是我为解决此问题所做的一个示例(不确定这是否是正确的答案,但它很简单并且有效!):

var home = 'index.html';
history.pushState({page:home}, null, home);

希望这可以帮助!

于 2013-01-08T21:02:45.323 回答
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);
}

您可以轻松扩展它以满足您的需求。

于 2015-07-17T14:12:47.887 回答
0

最后说:

我原以为浏览器会很乐意退回到由正常请求创建的条目。

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

您应该在您的网站第一次加载时保存状态,之后每次更改状态时

我对此进行了测试 - 它有效。

这意味着无需根据 window.location 加载您的内容

我希望我不会误导。

于 2016-01-06T19:50:41.990 回答