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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14215079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 16:03:18  来源:igfitidea点击:

Ajax with history.pushState and popstate - what do I do when popstate state property is null?

javascriptjqueryhtmlbrowser-historyhtml5-history

提问by And Finally

I'm trying out the HTML5 history API with ajax loading of content.

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

I've got a bunch of test pages connected by relative links. I have this JS, which handles clicks on those links. When a link is clicked the handler grabs its href attribute and passes it to ajaxLoadPage(), which loads content from the requested page into the content area of the current page. (My PHP pages are set up to return a full HTML page if you request them normally, but only a chunk of content if ?fragment=true is appended to the URL of the request.)

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

Then my click handler calls history.pushState() to display the URL in the address bar and add it to the browser history.

然后我的点击处理程序调用 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);
            }
        }
    });

});

When you add URLs to the history with pushState you also need to include an event handler for the popstate event to deal with clicks on the back or forward buttons. (If you don't do this, clicking back shows the URL you pushed to history in the address bar, but the page isn't updated.) So my popstate handler grabs the URL saved in the state property of each entry I created, and passes it to ajaxLoadPage to load the appropriate content.

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

This works OK for pages my click handler added to the history. But what happens with pages the browser added to history when I requested them "normally"? Say I land on my first page normally and then navigate through my site with clicks that do that ajax loading - if I then try to go back through the history to that first page, the last click shows the URL for the first page, but doesn't load the page in the browser. Why is that?

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

I can sort of see this has something to do with the state property of that last popstate event. The state property is null for that event, because it's only entries added to the history by pushState() or replaceState() that can give it a value. But my first loading of the page was a "normal" request - how come the browser doesn't just step back and load the initial URL normally?

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

采纳答案by And Finally

I still don't understand why the back button behaves like this - I'd have thought the browser would be happy to step back to an entry that was created by a normal request. Maybe when you insert other entries with pushState the history stops behaving in the normal way. But I found a way to make my code work better. You can't always depend on the state property containing the URL you want to step back to. But stepping back through history changes the URL in the address bar as you would expect, so it may be more reliable to load your content based on window.location. Following this great exampleI've changed my popstate handler so it loads content based on the URL in the address bar instead of looking for a URL in the state property.

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

One thing you have to watch out for is that some browsers (like Chrome) fire a popstate event when you initially hit a page. When this happens you're liable to reload your initial page's content unnecessarily. So I've added some bits of code from the excellent pjaxto ignore that initial pop.

您必须注意的一件事是,某些浏览器(如 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);

    });

});

One improvement you could make to this JS is only to attach the click event handler if the browser supports the history API.

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

回答by Matt

This is an older question but there is a much simpler answer using native javascript for this issue.

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

For the initial state you should not be using history.pushStatebut rather history.replaceState.

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

All arguments are the same for both methods with the only difference is that pushStatecreates a NEW history record and thus is the source of your problem. replaceStateonly replaces the state of that history record and will behave as expected, that is go back to the initial starting page.

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

回答by Erin

I ran into the same issue as the original question. This line

我遇到了与原始问题相同的问题。这条线

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

should be changed to

应该改为

var initialPop = !popped;

This is sufficient to catch the initial pop. Then you do not need to add the original page to the pushState. i.e. remove the following:

这足以捕捉最初的流行音乐。那么您不需要将原始页面添加到pushState. 即删除以下内容:

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

The final code based on AJAX tabs (and using Mootools):

基于 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');
       }
   });
}

回答by mattblac

I actually found myself with a similar need today and found the code you provided to be very useful. I came to the same problem you did, and I believe all that you're missing is pushing your index file or home page to the history in the same manner that you are all subsequent pages.

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

Here is an example of what I did to resolve this (not sure if it's the RIGHT answer, but it's simple and it works!):

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

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

Hope this helps!

希望这可以帮助!

回答by r3wt

I realize this is an old question, but when trying to manage state easily like this, it might be better to take the following approach:

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

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

in this way, you can specify an optional callback function on the state object when adding to history, then when popstateis trigger, this function would be called with the state object as a parameter.

这样,您可以在添加到历史记录时在状态对象上指定一个可选的回调函数,然后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);
}

You could easily extend this to suit your needs.

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

回答by olvin

And Finallysays:

最后说:

I'd have thought the browser would be happy to step back to an entry that was created by a normal request.

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

I found an explanation of that strange browser's behavior here. The explanation is

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

you should save the state when your site is loaded the first timeand thereafter every time it changes state

您应该在第一次加载站点时保存状态,此后每次更改状态时都应保存状态

I tested this - it works.

我测试了这个 - 它有效。

It means there is no need in loading your content based on window.location.

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

I hope I don't mislead.

我希望我没有误导。