Programming

Chrome에서 페이지로드시 Popstate

procodes 2020. 8. 28. 08:19
반응형

Chrome에서 페이지로드시 Popstate


내 웹 앱에 History API를 사용하고 있는데 한 가지 문제가 있습니다. Ajax 호출을 통해 페이지의 일부 결과 를 업데이트하고 페이지를 다시로드하지 않고 브라우저의 위치 표시 줄을 업데이트하기 위해 history.pushState () 를 사용합니다. 물론 back-button을 눌렀을 때 이전 상태를 복원하기 위해 window.popstate사용 합니다.

문제는 잘 알려져 있습니다. Chrome과 Firefox는 해당 popstate 이벤트를 다르게 처리합니다. Firefox는 첫 번째로드에서 실행되지 않지만 Chrome은 실행합니다. Firefox 스타일을 원하고로드시 정확히 동일한 결과로 결과를 업데이트하기 때문에로드시 이벤트를 발생시키지 않고 싶습니다. History.js를 사용하는 것을 제외하고 해결 방법이 있습니까? 내가 그것을 사용하고 싶지 않은 이유는 – 그것은 너무 많은 JS 라이브러리가 필요하고 이미 너무 많은 JS가있는 CMS에서 구현되어야하기 때문에 내가 넣는 JS를 최소화하고 싶습니다. .

따라서로드시 Chrome이 'popstate'를 실행하지 않도록하는 방법이 있는지, 아니면 모든 라이브러리가 하나의 파일로 합쳐진 것처럼 누군가가 History.js를 사용하려고 시도했는지 알고 싶습니다.


버전 19의 Google 크롬에서 @spliter의 솔루션이 작동을 멈췄습니다. @johnnymire가 지적했듯이 Chrome 19의 history.state는 존재하지만 null입니다.

내 해결 방법은 window.history.state! == null 을 추가하여 window.history에 상태가 있는지 확인하는 것입니다.

var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;

모든 주요 브라우저와 Chrome 버전 19 및 18에서 테스트했습니다. 작동하는 것 같습니다.


onpopstate에 추가하는 각 핸들러에 대해 특별한 조치를 취하고 싶지 않은 경우 내 솔루션이 흥미로울 수 있습니다. 이 솔루션의 큰 장점은 페이지 로딩이 완료되기 전에 onpopstate 이벤트를 처리 할 수 ​​있다는 것입니다.

onpopstate 핸들러를 추가하기 전에이 코드를 한 번만 실행하면 모든 것이 예상대로 작동합니다 (일명 Mozilla ^^) .

(function() {
    // There's nothing to do for older browsers ;)
    if (!window.addEventListener)
        return;
    var blockPopstateEvent = document.readyState!="complete";
    window.addEventListener("load", function() {
        // The timeout ensures that popstate-events will be unblocked right
        // after the load event occured, but not in the same event-loop cycle.
        setTimeout(function(){ blockPopstateEvent = false; }, 0);
    }, false);
    window.addEventListener("popstate", function(evt) {
        if (blockPopstateEvent && document.readyState=="complete") {
            evt.preventDefault();
            evt.stopImmediatePropagation();
        }
    }, false);
})();

작동 원리 :

Chrome , Safari 및 기타 웹킷 브라우저는 문서가로드 될 때 onpopstate 이벤트를 발생시킵니다. 이것은 의도 된 것이 아니므로 문서가로드 된 후 첫 번째 이벤트 루프 시클이 발생할 때까지 popstate 이벤트를 차단합니다. 이는 preventDefault 및 stopImmediatePropagation 호출에 의해 수행됩니다 (stopPropagation stopImmediatePropagation이 모든 이벤트 처리기 호출을 즉시 중지하는 것과 달리).

그러나 Chrome이 onpopstate를 잘못 실행하면 문서의 readyState가 이미 '완료'상태이므로 문서가로드되기 전에 onpopstate 호출을 허용하기 위해 문서로드가 완료되기 전에 실행 된 opopstate 이벤트를 허용합니다.

업데이트 2014-04-23 : 페이지가로드 된 후 스크립트가 실행되면 popstate 이벤트가 차단되는 버그가 수정되었습니다.


콘텐츠가로드되는 데 얼마나 오래 걸릴지 모르기 때문에 setTimeout 만 사용하는 것은 올바른 해결책이 아니므로 시간 초과 후에 popstate 이벤트가 발생할 수 있습니다.

내 솔루션은 다음과 같습니다. https://gist.github.com/3551566

/*
* Necessary hack because WebKit fires a popstate event on document load
* https://code.google.com/p/chromium/issues/detail?id=63040
* https://bugs.webkit.org/process_bug.cgi
*/
window.addEventListener('load', function() {
  setTimeout(function() {
    window.addEventListener('popstate', function() {
      ...
    });
  }, 0);
});

해결책은 jquery.pjax.js 줄 195-225 에서 발견되었습니다 .

// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href


// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(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

  var state = event.state

  if ( state && state.pjax ) {
    var container = state.pjax
    if ( $(container+'').length )
      $.pjax({
        url: state.url || location.href,
        fragment: state.fragment,
        container: container,
        push: false,
        timeout: state.timeout
      })
    else
      window.location = location.href
  }
})

pjax를 다시 구현하는 것보다 더 직접적인 솔루션은 pushState에 변수를 설정하고 popState에서 변수를 확인하여 초기 popState가로드시 일관성없이 실행되지 않도록하는 것입니다 (jquery 관련 솔루션이 아니라 이벤트에만 사용).

$(window).bind('popstate', function (ev){
  if (!window.history.ready && !ev.originalEvent.state)
    return; // workaround for popstate on load
});

// ... later ...

function doNavigation(nextPageId) {
  window.history.ready = true;

  history.pushState(state, null, 'content.php?id='+ nextPageId); 
  // ajax in content instead of loading server-side
}

Webkit의 초기 onpopstate 이벤트에는 할당 된 상태가 없으므로이를 사용하여 원하지 않는 동작을 확인할 수 있습니다.

window.onpopstate = function(e){
    if(e.state)
        //do something
};

원래 페이지로 다시 이동할 수있는 포괄적 인 솔루션은 다음 아이디어를 기반으로합니다.

<body onload="init()">
    <a href="page1" onclick="doClick(this); return false;">page 1</a>
    <a href="page2" onclick="doClick(this); return false;">page 2</a>
    <div id="content"></div>
</body>

<script>
function init(){
   openURL(window.location.href);
}
function doClick(e){
    if(window.history.pushState)
        openURL(e.getAttribute('href'), true);
    else
        window.open(e.getAttribute('href'), '_self');
}
function openURL(href, push){
    document.getElementById('content').innerHTML = href + ': ' + (push ? 'user' : 'browser'); 
    if(window.history.pushState){
        if(push)
            window.history.pushState({href: href}, 'your page title', href);
        else
            window.history.replaceState({href: href}, 'your page title', href);
    }
}
window.onpopstate = function(e){
    if(e.state)
        openURL(e.state.href);
};
</script>

While this could still fire twice (with some nifty navigation), it can be handled simply with a check against the previous href.


This is my workaround.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);

Here's my solution:

var _firstload = true;
$(function(){
    window.onpopstate = function(event){
        var state = event.state;

        if(_firstload && !state){ 
            _firstload = false; 
        }
        else if(state){
            _firstload = false;
            // you should pass state.some_data to another function here
            alert('state was changed! back/forward button was pressed!');
        }
        else{
            _firstload = false;
            // you should inform some function that the original state returned
            alert('you returned back to the original state (the home state)');
        }
    }
})   

The best way to get Chrome to not fire popstate on a page load is to up-vote https://code.google.com/p/chromium/issues/detail?id=63040. They've known Chrome isn't in compliance with the HTML5 spec for two full years now and still haven't fixed it!


In case of use event.state !== null returning back in history to first loaded page won't work in non mobile browsers. I use sessionStorage to mark when ajax navigation really starts.

history.pushState(url, null, url);
sessionStorage.ajNavStarted = true;

window.addEventListener('popstate', function(e) {
    if (sessionStorage.ajNavStarted) {
        location.href = (e.state === null) ? location.href : e.state;
    }
}, false);


The presented solutions have a problem on page reload. The following seems to work better, but I have only tested Firefox and Chrome. It uses the actuality, that there seems to be a difference between e.event.state and window.history.state.

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});

I know you asked against it, but you should really just use History.js as it clears up a million browser incompatibilities. I went the manual fix route only to later find there were more and more problems that you'll only find out way down the road. It really isn't that hard nowadays:

<script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>

And read the api at https://github.com/browserstate/history.js


This solved the problem for me. All I did was set a timeout function which delays the execution of the function long enough to miss the popstate event that is fired on pageload

if (history && history.pushState) {
  setTimeout(function(){
    $(window).bind("popstate", function() {
      $.getScript(location.href);
    });
  },3000);
}

You can create an event and fire it after your onload handler.

var evt = document.createEvent("PopStateEvent");
evt.initPopStateEvent("popstate", false, false, { .. state object  ..});
window.dispatchEvent(evt);

Note, this is slightly broke in Chrome/Safari, but I have submitted the patch in to WebKit and it should be available soon, but it is the "most correct" way.


This worked for me in Firefox and Chrome

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};

참고URL : https://stackoverflow.com/questions/6421769/popstate-on-pages-load-in-chrome

반응형