history.pushState를 통해 히스토리 변경 사항에 대한 알림을받는 방법은 무엇입니까?


154

이제 HTML5 history.pushState가 브라우저 기록을 변경하기 위해 도입 했으므로 웹 사이트는 URL의 조각 식별자를 변경하는 대신 Ajax와 함께 이것을 사용하기 시작합니다.

안타깝게도이 전화를 더 이상 감지 할 수 없습니다 onhashchange.

내 질문은 : 웹 사이트가 history.pushState언제 사용되는지 감지하는 신뢰할 수있는 방법이 있습니까? 사양에는 발생한 이벤트에 대해 언급되어 있지 않습니다 (적어도 아무것도 찾을 수 없습니다).
파사드를 만들려고했는데 window.history내 고유의 JavaScript 객체 로 바뀌 었지만 전혀 효과가 없었습니다.

추가 설명 : 이 변경 사항을 감지하고 그에 따라 행동 해야하는 Firefox 추가 기능을 개발 중입니다.
며칠 전에 일부 DOM 이벤트를 듣는 것이 효과적 일지에 대한 비슷한 질문이 있었지만 이러한 이벤트가 여러 가지 이유로 생성 될 수 있기 때문에 그에 의존하지 않을 것입니다.

최신 정보:

다음은 호출 onpopstate될 때 트리거되지 않는 것을 보여주는 jsfiddle (Firefox 4 또는 Chrome 8 사용)입니다 pushState(또는 내가 잘못하고 있습니까? 자유롭게 개선하십시오!).

업데이트 2 :

또 다른 (측면) 문제는 window.location사용할 때 업데이트되지 않는다는 것 입니다 pushState(그러나 이미 여기에 대해 읽었습니다).

답변:


194

5.5.9.1 이벤트 정의

popstate 세션 기록 항목을 탐색 할 때 이벤트는 어떤 경우에 발생합니다.

이것에 따르면,를 사용할 때 popstate가 시작될 이유가 없습니다 pushState. 그러나 같은 행사 pushstate가 도움이 될 것입니다. history호스트 개체 이므로 주의해야하지만이 경우 Firefox가 좋은 것 같습니다. 이 코드는 잘 작동합니다.

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

귀하의 jsfiddle 다음과 같습니다.

window.onpopstate = history.onpushstate = function(e) { ... }

window.history.replaceState같은 방법으로 원숭이 패치 를 할 수 있습니다 .

참고 : 물론 onpushstate전역 객체 에 간단하게 추가 할 수 있으며 더 많은 이벤트를 처리하도록 할 수도 있습니다add/removeListener


당신은 당신이 전체 history개체 를 교체했다고 말했다 . 이 경우에는 불필요 할 수 있습니다.
gblazex

@galambalazs : 그렇습니다. 어쩌면 (모르지 마십시오) window.history읽기 전용이지만 기록 개체의 속성이 아닙니다 ...이 솔루션에 대해 무리 감사합니다 :)
Felix Kling

사양에 따르면 "pagetransition"이벤트가 있지만 아직 구현되지 않은 것 같습니다.
Mohamed Mansour

2
@ user280109-내가 아는 경우 알려줄 것입니다. :) 나는 Opera atm에서 이것을 할 수있는 방법이 없다고 생각합니다.
gblazex

1
@cprcrack 전역 범위를 깨끗하게 유지하기위한 것입니다. pushState나중에 사용하기 위해 기본 메소드 를 저장해야 합니다. 그래서 그 대신 전역 변수의 나는 인생에 전체 코드를 캡슐화하기로 결정했습니다 : en.wikipedia.org/wiki/Immediately-invoked_function_expression
gblazex을

4

나는 이것을 사용했다 :

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});

galambalazs 와 거의 동일 합니다.

그것은 일반적으로 과잉입니다. 모든 브라우저에서 작동하지 않을 수 있습니다. (내 브라우저 버전 만 신경 쓰고 있습니다.)

(그리고 var를 남기 _wr므로 랩이나 무언가를 감싸고 싶을 수도 있습니다. 나는 그것에 대해 신경 쓰지 않았습니다.)


4

마침내 이것을하는 "올바른"방법을 찾았습니다! 확장 프로그램에 권한을 추가하고 콘텐츠 스크립트뿐만 아니라 백그라운드 페이지를 사용해야하지만 작동합니다.

원하는 이벤트 browser.webNavigation.onHistoryStateUpdated는 페이지에서 historyAPI를 사용하여 URL을 변경하면 시작됩니다. 액세스 권한이있는 사이트에 대해서만 실행되며, 필요한 경우 URL 필터를 사용하여 스팸을 더욱 줄일 수 있습니다. webNavigation권한 (및 관련 도메인에 대한 호스트 권한) 이 필요합니다 .

이벤트 콜백은 탭 ID, "탐색중인"URL 및 기타 세부 정보를 가져옵니다. 이벤트가 발생할 때 해당 페이지의 컨텐츠 스크립트에서 조치를 취해야하는 경우 백그라운드 페이지에서 직접 관련 스크립트를 삽입하거나 컨텐츠 스크립트 port가로드 될 때 백그라운드 페이지를 엽니 다 . 백그라운드 페이지를 저장하십시오. 컬렉션의 해당 포트를 탭 ID로 색인화하고 이벤트가 발생할 때 관련 포트 (백그라운드 스크립트에서 콘텐츠 스크립트로)를 통해 메시지를 보냅니다.


3

당신은 window.onpopstate이벤트에 바인딩 할 수 있습니까?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

문서에서 :

윈도우의 popstate 이벤트를위한 이벤트 핸들러.

활성 내역 항목이 변경 될 때마다 popstate 이벤트가 창에 전달됩니다. 활성화 된 히스토리 항목이 history.pushState ()에 대한 호출로 작성되었거나 history.replaceState ()에 대한 호출의 영향을받는 경우, popstate 이벤트의 state 특성에는 히스토리 항목의 상태 오브젝트 사본이 포함됩니다.


6
나는 이미 이것을 시도했고 사용자가 히스토리 (또는 history.go, .back) 중 하나 가 호출 될 때만 이벤트가 트리거됩니다 . 그러나 아닙니다 pushState. 여기를 테스트 할 내 시도는, 어쩌면 내가 뭔가 잘못을 수행 jsfiddle.net/fkling/vV9vd 그것은 단지 것을 그런 식으로 관련 보인다 경우 역사에 의해 변경된 pushState해당 상태 객체가 이벤트 핸들러에 전달되는 때 다른 방법 호출됩니다.
Felix Kling

1
아 이 경우 내가 생각할 수있는 유일한 것은 스택 크기가 변경된 경우 기록 스택의 길이를보고 이벤트를 발생시키는 시간 초과를 등록하는 것입니다.
stef

이것은 흥미로운 아이디어입니다. 유일한 것은 시간 초과가 자주 발생하여 사용자가 (긴) 지연을 느끼지 않도록해야한다는 것입니다 (새 URL에 대한 데이터를로드하고 표시해야 함). 나는 가능한 경우 항상 타임 아웃과 폴링을 피하려고 노력하지만 지금까지는 이것이 유일한 해결책 인 것 같습니다. 여전히 다른 제안을 기다릴 것입니다. 그러나 지금은 대단히 감사합니다!
Felix Kling

클릭 할 때마다 히스토리 스틱의 길이를 확인하는 것으로 충분할 수 있습니다.
Felix Kling

1
그렇습니다. 나는 이것을 가지고 놀았습니다. 하나의 문제는 스택의 크기가 변경되지 않았기 때문에 이벤트를 던지지 않는 replaceState 호출입니다.
stef

1

Firefox 애드온에 대해 질문하고 있으므로 여기에 작동해야 할 코드가 있습니다. 사용 unsafeWindow더 이상 권장되지 않으며 수정 후 클라이언트 스크립트에서 pushState를 호출하면 오류가 발생합니다.

속성 기록에 대한 액세스 권한이 거부되었습니다.

대신 다음 과 같이 함수를 삽입 할 수있는 exportFunction 이라는 API 가 있습니다 window.history.

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});

마지막 줄에서로 변경해야 unsafeWindow.history,합니다 window.history. unsafeWindow에서 작동하지 않았습니다.
Lindsay-Needs-Sleep

0

galambalazs의 답변 원숭이 패치 window.history.pushStatewindow.history.replaceState어떤 이유로 든 나를 위해 일을 중단했습니다. 다음은 폴링을 사용하기 때문에 좋지 않은 대안입니다.

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

폴링에 대한 대안이 있습니까?
SuperUberDuper

@SuperUberDuper : 다른 답변을보십시오.
Flimm

@Flimm Polling은 좋지 않지만 추악합니다. 그러나 글쎄, 당신은 당신이 말한 선택이 없었습니다.
야이로 프로

이것은 원숭이 패치보다 훨씬 낫습니다. 원숭이 패치는 다른 스크립트로 취소 할 수 있으며 알림이 없습니다.
Ivan Castellanos

0

이 주제에는보다 현대적인 솔루션이 필요하다고 생각합니다.

나는 nsIWebProgressListener주변에 있었을 것이라고 확신 했지만 아무도 그것을 언급하지 않은 것에 놀랐습니다.

프레임 스크립트에서 (e10s 호환) :

let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

그런 다음 onLoacationChange

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

그것은 분명히 모든 pushState를 잡을 것입니다. 그러나 "ALSO는 pushState를 트리거합니다"라는 주석 경고가 있습니다. 따라서 여기서 푸시 스테이트 상태가되도록 필터링을 더 수행해야합니다.

기반 : https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

그리고 : resource : //gre/modules/WebNavigationContent.js


이 대답은 내가 실수하지 않는 한 주석과 관련이 없습니다. WebProgressListener는 FF 확장을위한 것입니까? 이 질문은 HTML5 역사에 관한 것입니다
Kloar

@Kloar는 이러한 코멘트를 삭제하십시오
Noitidart

0

글쎄, 나는 pushState속성 을 대체하는 많은 예를 history보았지만 좋은 생각인지 확실하지 않다. 나는 푸시 상태뿐만 아니라 상태 바꾸기를 제어 할 수있는 히스토리와 비슷한 API를 기반으로 서비스 이벤트를 생성하는 것을 선호한다 또한 글로벌 히스토리 API에 의존하지 않는 다른 많은 구현의 문을 열어줍니다. 다음 예를 확인하십시오.

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

EventEmitter정의 가 필요한 경우 위 코드는 NodeJS 이벤트 이미 터 ( https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js )를 기반으로합니다 . utils.inherits구현은 여기에서 찾을 수 있습니다 : https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970


요청한 정보로 답변을 편집했습니다. 확인하십시오!
빅터 Queiroz

@VictorQueiroz 기능을 캡슐화하는 멋지고 깨끗한 아이디어입니다. 그러나 일부 타사 라이브러리에서 pushState를 호출하면 HistoryApi에 알림이 표시되지 않습니다.
야이로 프로

0

오히려 기본 기록 방법을 덮어 쓰지 않기 때문에이 간단한 구현은 이벤트를 전달하고 history.pushState ()를 반환하는 eventedPush state라는 자체 함수를 만듭니다. 어느 쪽이든 잘 작동하지만 네이티브 개발자가 미래 개발자가 기대하는대로 계속 수행 할 것이므로이 구현이 약간 더 깨끗합니다.

function eventedPushState(state, title, url) {
    var pushChangeEvent = new CustomEvent("onpushstate", {
        detail: {
            state,
            title,
            url
        }
    });
    document.dispatchEvent(pushChangeEvent);
    return history.pushState(state, title, url);
}

document.addEventListener(
    "onpushstate",
    function(event) {
        console.log(event.detail);
    },
    false
);

eventedPushState({}, "", "new-slug"); 

0

@gblazex가 제공 한 솔루션을 기반으로 동일한 접근법을 따르고 싶지만 화살표 기능을 사용하려는 경우 자바 스크립트 논리에서 아래 예제를 따르십시오.

private _currentPath:string;    
((history) => {
          //tracks "forward" navigation event
          var pushState = history.pushState;
          history.pushState =(state, key, path) => {
              this._notifyNewUrl(path);
              return pushState.apply(history,[state,key,path]); 
          };
        })(window.history);

//tracks "back" navigation event
window.addEventListener('popstate', (e)=> {
  this._onUrlChange();
});

그런 다음 _notifyUrl(url)현재 페이지 URL이 업데이트 될 때 (페이지가 전혀로드되지 않은 경우에도) 필요한 조치를 트리거하는 다른 함수 를 구현 하십시오.

  private _notifyNewUrl (key:string = window.location.pathname): void {
    this._path=key;
    // trigger whatever you need to do on url changes
    console.debug(`current query: ${this._path}`);
  }

0

방금 새 URL을 원했기 때문에 @gblazex 및 @Alberto S.의 코드를 조정하여 다음을 얻었습니다.

(function(history){

  var pushState = history.pushState;
    history.pushState = function(state, key, path) {
    if (typeof history.onpushstate == "function") {
      history.onpushstate({state: state, path: path})
    }
    pushState.apply(history, arguments)
  }
  
  window.onpopstate = history.onpushstate = function(e) {
    console.log(e.path)
  }

})(window.history);

0

필자는 가능한 경우에도 기본 함수를 수정하는 것이 좋지 않다고 생각하며 항상 응용 프로그램 범위를 유지해야하므로 전역 방식으로 전역 pushState 함수를 사용하지 않는 것이 좋습니다. 대신 자신 중 하나를 사용하십시오.

function historyEventHandler(state){ 
    // your stuff here
} 

window.onpopstate = history.onpushstate = historyEventHandler

function pushHistory(...args){
    history.pushState(...args)
    historyEventHandler(...args)
}
<button onclick="pushHistory(...)">Go to happy place</button>

다른 코드가 기본 pushState 함수를 사용하는 경우 이벤트 트리거가 발생하지 않습니다 (이 경우 코드를 확인해야 함).


-5

표준 상태로서 :

history.pushState () 또는 history.replaceState () 만 호출하면 popstate 이벤트가 트리거되지 않습니다. popstate 이벤트는 뒤로 버튼 클릭 (또는 JavaScript에서 history.back () 호출)과 같은 브라우저 작업을 수행해야만 트리거됩니다.

우리는 history.back ()을 호출하여 WindowEventHandlers.onpopstate를 trigeer해야합니다.

그래서 :

history.pushState(...)

하다:

history.pushState(...)
history.pushState(...)
history.back()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.