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

Posted

技术标签:

【中文标题】带有 history.pushState 和 popstate 的 Ajax - 当 popstate 状态属性为空时我该怎么办?【英文标题】:Ajax with history.pushState and popstate - what do I do when popstate state property is null? 【发布时间】:2012-12-22 07:28:31 【问题描述】:

我正在尝试使用 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?

【问题讨论】:

【参考方案1】:

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

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

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

希望这会有所帮助!

【讨论】:

感谢 mattblac,我找到了另一种可能更灵活的方法,我会在这里发布。【参考方案2】:

我仍然不明白为什么后退按钮会有这样的行为 - 我原以为浏览器会很乐意退回到由正常请求创建的条目。也许当您使用 pushState 插入其他条目时,历史记录会停止以正常方式运行。但我找到了一种让我的代码更好地工作的方法。您不能总是依赖于包含要退回的 URL 的 state 属性。但是回溯历史会如您所愿地更改地址栏中的 URL,因此基于 window.location 加载您的内容可能更可靠。在great example 之后,我更改了我的 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 进行的一项改进是仅附加点击事件处理程序。

【讨论】:

它有一个小问题,如果你点击页面重新加载,它只会加载页面的 ajax 部分。我想知道是否有人有页面重新加载的解决方案。 @Hydrino 使用正确的 URL 推送状态并使用 AJAX 加载部分页面。【参考方案3】:

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

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');
       
   );

【讨论】:

太棒了!很好的答案艾琳。【参考方案4】:

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

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

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

【讨论】:

这完全是我问题的答案。当您第一次来到页面,并使用 ajax 加载它时,您需要使用这个替换第一个状态。谢谢!【参考方案5】:

我意识到这是一个老问题,但是当尝试像这样轻松管理状态时,采用以下方法可能会更好:

$(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);

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

【讨论】:

【参考方案6】:

最后说:

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

我找到了对那个奇怪的浏览器行为here 的解释。解释是

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

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

这意味着不需要根据window.location加载你的内容

希望我没有误导。

【讨论】:

以上是关于带有 history.pushState 和 popstate 的 Ajax - 当 popstate 状态属性为空时我该怎么办?的主要内容,如果未能解决你的问题,请参考以下文章

H5,API的pushState(),replaceState()和popstate()用法

前端路由的实现 —— History的pushState和replaceState用法

history.pushstate和history.replacestate的区别

如何检测何时使用 history.pushState 和 history.replaceState? [复制]

使用ajax和history.pushState无刷新改变页面URL

为 Angular 2 配置 history.pushState