Chrome에서 페이지로드시 Popstate


94

내 웹 앱에 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를 사용하려고 시도했는지 알고 싶습니다.


3
추가하려면 Firefox의 동작은 구체적이고 올바른 동작입니다. Chrome 34부터는 이제 수정되었습니다! 오페라 개발자를위한 예이!
Henry Chan

답변:


49

버전 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에서 테스트했습니다. 작동하는 것 같습니다.


5
알림 주셔서 감사합니다. 나는 당신 이 그것을 처리 할 것이므로 단순화 in하고 null확인할 수 있다고 생각합니다 . != nullundefined
pimvdb

5
이것을 사용하는 것은 항상 같은 방법 null에서 사용하는 경우에만 작동합니다 . 물론 그것은 아마도 대부분의 사용 일 것입니다 (jquery.pjax.js 및 대부분의 다른 데모에서 설정되는 방법입니다). 그러나 브라우저가 구현하는 경우 (FF와 크롬 19+ 등), window.history.state은 브라우저를 다시 시작 새로 고침 일 이후에 null이 아닌 수history.pushState()history.pushState(null,null,'http//example.com/)window.history.state
unexplainedBacn

19
이것은 Chrome 21에서 더 이상 작동하지 않습니다. 페이지로드시 popped를 false로 설정 한 다음 모든 푸시 또는 팝에서 popped를 true로 설정했습니다. 이 중성 크롬은 첫 번째로드시 팝업됩니다. 그것의 더 나은 여기에 조금 설명 : github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
차드 폰 나우

1
@ChadvonNau 훌륭한 아이디어, 그리고 그것은 치료를 작동합니다-대단히 감사합니다!
sowasred2012

나는이 같은 문제를 가지고 간단한 @ChadvonNau 솔루션을 사용하여 끝냈습니다.
Pherrymason 2013-06-17

30

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 이벤트가 차단되는 버그가 수정되었습니다.


4
지금까지 최고의 솔루션입니다. 몇 년 동안 setTimeout 메서드를 사용해 왔지만 페이지가 느리게로드되거나 사용자가 pushstate를 매우 빠르게 트리거 할 때 버그가 발생합니다. window.history.state상태가 설정된 경우 다시로드 할 때 실패합니다. pushState 전에 변수를 설정하면 코드가 복잡해집니다. 솔루션은 우아하고 나머지 코드베이스에 영향을주지 않고 다른 스크립트 위에 놓을 수 있습니다. 감사.
KIK

4
이것이 해결책입니다! 이 문제를 해결하려고 할 때 며칠 전에 이것을 읽었다면. 이것은 완벽하게 작동 한 유일한 것입니다. 감사합니다!
Ben Fleming 2014

1
훌륭한 설명과 시도해 볼만한 유일한 솔루션입니다.
Sunny R Gupta

이것은 훌륭한 솔루션입니다. 글을 쓰는 시점에서 최신 크롬 및 FF 문제는 사파리 (mac 및 iphone)에 있습니다. 이 솔루션은 매력처럼 작동합니다.
Vin

1
우와! Safari가 당신의 하루를 망치고 있다면, 이것이 당신이 찾고있는 코드입니다!
DanO 2015

28

콘텐츠가로드되는 데 얼마나 오래 걸릴지 모르기 때문에 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);
});

이것은 나를 위해 일한 유일한 솔루션입니다. 감사!
저스틴

3
단! 가장 많이 찬성 및 / 또는 수락 된 답변보다 더 잘 작동합니다!
ProblemsOfSumit

누구든지이 솔루션에 대한 활성 링크가 있습니까? 주어진 링크 gist.github.com/3551566
Harbir

1
요점이 필요한 이유는 무엇입니까?
Oleg Vaskevich 2014

완벽한 대답입니다. 감사.
sideroxylon

25

해결책은 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
  }
})

8
이 솔루션은 Windows (Chrome 19)에서 작동을 중지했지만 Mac (Chrome 18)에서는 계속 작동합니다. history.state가 크롬 (19)에 존재처럼 보인다는 아니지만 18
johnnymire

1
@johnnymire Chrome 19에 대한 내 솔루션을 게시했습니다. stackoverflow.com/a/10651028/291500
Pavel Linkesch

누군가가이 답변을 수정하여이 Chrome 19 이후 동작을 반영해야합니다.
Jorge Suárez de Lis 2013 년

5
솔루션을 찾는 모든 사람에게 Torben의 답변은 오늘 현재 Chrome 및 Firefox 버전에서 매력적으로 작동합니다
Jorge Suárez de Lis 2013 년

1
이제 2015 년 4 월에이 솔루션을 사용하여 Safari 문제를 해결했습니다. Cordova를 사용하는 하이브리드 iOS / Android 앱에서 뒤로 버튼을 눌러 처리하기 위해 onpopstate 이벤트를 사용해야했습니다. Safari는 내가 프로그래밍 방식으로 호출 한 새로 고침 후에도 onpopstate를 실행합니다. 내 코드가 설정되는 방식으로 reload ()가 호출 된 후 무한 루프에 들어갔습니다. 이것은 그것을 고쳤습니다. 이벤트 선언 후 처음 3 줄만 필요했습니다 (위에 할당 포함).
Martavis P. 2015

14

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
}

항상 거짓으로 평가된다면 그렇지 않습니까? 내 말은, window.history.ready항상 사실입니다.
Andriy Drozdyuk

예제를 좀 더 명확하게 업데이트했습니다. 기본적으로 모든 것을 명확하게 한 후에 만 ​​popstate 이벤트를 처리하기를 원합니다. 로드 후 500ms 후에 setTimeout으로 동일한 효과를 얻을 수 있지만 솔직히 말하면 약간 저렴합니다.
Tom McKenzie

3
이 IMO 답을해야한다, 나는 Pavels 솔루션을 시도하고 파이어 폭스에서 제대로 작동하지 않았다
붉은 돼지

이것은이 성가신 문제에 대한 쉬운 해결책으로 저에게 효과적이었습니다. 감사합니다!
nirazul

!window.history.ready && !ev.originalEvent.state-이 두 가지는 페이지를 새로 고칠 때 정의 되지 않습니다 . 따라서 코드가 실행되지 않습니다.
chovy

4

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>

이 동안 여전히 (일부 멋진 탐색)를 두 번 화재, 그것은 이전의 HREF에 대한 검사로 간단하게 처리 할 수 있습니다.


1
감사. pjax 솔루션은 iOS window.history.state에서도 null 이기 때문에 iOS 5.0에서 작동을 중지했습니다 . e.state 속성이 null인지 확인하는 것만으로 충분합니다.
Husky

이 솔루션에 알려진 문제가 있습니까? 내가 테스트하는 모든 브라우저에서 완벽하게 작동합니다.
Jacob Ewing 2014

3

이것이 내 해결 방법입니다.

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

Ubuntu의 Chromium 28에서는 나에게 적합하지 않습니다. 때때로 이벤트가 여전히 시작됩니다.
Jorge Suárez de Lis 2013 년

직접 시도했지만 항상 작동하지는 않습니다. 때로는 popstate 초기 페이지로드에 따라 제한 시간보다 늦게 화재
앤드류 시민

1

내 해결책은 다음과 같습니다.

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


0

사용하는 경우 event.state !== null기록에서 처음으로로드 된 페이지로 돌아가는 기능은 모바일이 아닌 브라우저에서 작동하지 않습니다. sessionStorage를 사용하여 Ajax 탐색이 실제로 시작되는시기를 표시합니다.

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


window.onload = function () {if (history.state === null) {history.replaceState (location.pathname + location.search + location.hash, null, location.pathname + location.search + location.hash); }}; window.addEventListener ( 'popstate', function (e) {if (e.state! == null) {location.href = e.state;}}, false);
ilusionist apr

-1

제시된 솔루션은 페이지 새로 고침에 문제가 있습니다. 다음은 더 잘 작동하는 것 같지만 Firefox와 Chrome 만 테스트했습니다. e.event.state와 사이에 차이가있는 것처럼 보이는 현실을 사용합니다 window.history.state.

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

e.event정의되지 않는다
chovy

-1

나는 당신이 그것에 대해 물었다는 것을 알고 있지만, 당신은 정말로 단지 History.js를 사용해야합니다. 그것은 수백만 개의 브라우저 비 호환성을 없애기 때문입니다. 나는 당신이 길을 따라야 만 알아낼 문제가 점점 더 많이 있다는 것을 나중에 발견하기 위해서만 수동 수정 경로를 갔다. 요즘은 그렇게 어렵지 않습니다.

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

https://github.com/browserstate/history.js 에서 api를 읽으십시오.


-1

이것은 나를 위해 문제를 해결했습니다. 내가 한 것은 페이지로드에서 발생하는 popstate 이벤트를 놓칠 수있을만큼 오랫동안 함수 실행을 지연시키는 시간 제한 함수를 설정 한 것뿐입니다.

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

-2

이벤트를 생성하고 onload 핸들러 후에 실행할 수 있습니다.

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

이것은 Chrome / Safari에서 약간 손상되었지만 WebKit에 패치를 제출했으며 곧 제공 될 예정이지만 "가장 정확한"방법입니다.


-2

이것은 Firefox와 Chrome에서 나를 위해 일했습니다.

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.