Zdarzenie podczas zmiany window.location.href


88

Piszę skrypt Greasemonkey dla witryny, która w pewnym momencie się modyfikuje location.href.

Jak mogę uzyskać zdarzenie (przez window.addEventListenerlub coś podobnego) w przypadku window.location.hrefzmian na stronie? Potrzebuję również dostępu do DOM dokumentu wskazującego na nowy / zmodyfikowany adres URL.

Widziałem inne rozwiązania, które obejmują limity czasu i sondowanie, ale chciałbym tego uniknąć, jeśli to możliwe.


Odpowiedzi:


90

zdarzenie popstate :

Zdarzenie popstate jest uruchamiane po zmianie aktywnego wpisu historii. [...] Zdarzenie popstate jest wywoływane tylko przez wykonanie czynności przeglądarki, takiej jak kliknięcie przycisku Wstecz (lub wywołanie history.back () w JavaScript)

Tak więc nasłuchiwanie popstatezdarzenia i wysyłanie popstatezdarzenia podczas używania history.pushState()powinno wystarczyć do podjęcia działań w przypadku hrefzmiany:

window.addEventListener('popstate', listener);

const pushUrl = (href) => {
  history.pushState({}, '', href);
  window.dispatchEvent(new Event('popstate'));
};

2
Ale czy popstate uruchomi się przed załadowaniem zawartości?
user2782001

4
bezużyteczne, jeśli nie masz żadnej kontroli nad fragmentem kodu, którypushState
Nathan Gouy

2
To nie zawsze działa, polega na wywołaniu zdarzenia popstate. Na przykład nawigacja w YouTube nie zostanie uruchomiona, tylko jeśli naciśniesz wstecz lub do przodu w historii
TrySpace

57

Używam tego skryptu w moim rozszerzeniu „Grab Any Media” i działa dobrze ( jak w przypadku YouTube )

var oldHref = document.location.href;

window.onload = function() {

    var
         bodyList = document.querySelector("body")

        ,observer = new MutationObserver(function(mutations) {

            mutations.forEach(function(mutation) {

                if (oldHref != document.location.href) {

                    oldHref = document.location.href;

                    /* Changed ! your code here */

                }

            });

        });

    var config = {
        childList: true,
        subtree: true
    };

    observer.observe(bodyList, config);

};

Jakoś podoba mi się to podejście ... wydaje się trochę RxJs-ish :)
Guntram

Jedyne rozwiązanie, które mogło zadziałać w YouTube: [Tylko raport] Odmówiono utworzenia pracownika z „ youtube.com/sw.js ”, ponieważ narusza ono następującą dyrektywę Polityki bezpieczeństwa treści: „worker-src 'none'”.
Simon Meusel

MutationObserver

1
Działa po wyjęciu z pudełka bez żadnych problemów w moim przypadku, niech Bóg błogosławi! To irytujące, że nie ma jeszcze żadnego rodzimego wydarzenia (dla mnie popstate nie działa), ale pewnego dnia! Chciałbym używać window.addEventListener("load", () => {})zamiast window.onload chociaż :)
Sanchit Batra

to działa, wykrywa historię.pushState nawet w kontekście rozszerzenia
YangombiUmpakati

29

Nie możesz uniknąć sondowania, nie ma żadnego zdarzenia dla zmiany href.

Używanie interwałów jest i tak dość lekkie, jeśli nie przesadzisz. Sprawdzanie href co około 50 ms nie będzie miało znaczącego wpływu na wydajność, jeśli się o to martwisz.


3
@AlexanderMills: na szczęście są te nowe rozwiązania popstatei hashchange.
serv-inc

1
To nie jest prawda.
inf3rno

@ MartinZvarík Nawet w 2010 r. Można było użyć zdarzenia unload oraz zadania mikro lub makro, aby załadować nowy dokument.
inf3rno

3
Niestety ta odpowiedź jest nadal poprawna w 2020 roku. Popstate uruchamia się tylko wtedy, gdy użytkownik używa przycisków do przodu / wstecz, a hashchange uruchamia się tylko dla zmian hash. Jeśli nie masz kontroli nad kodem, który powoduje zmianę lokalizacji, musisz sondować 🤷‍♀️
Rico Kahler

14

Istnieje domyślne onhashchangezdarzenie, którego możesz użyć.

Udokumentowane TUTAJ

I może być używany w ten sposób:

function locationHashChanged( e ) {
    console.log( location.hash );
    console.log( e.oldURL, e.newURL );
    if ( location.hash === "#pageX" ) {
        pageX();
    }
}

window.onhashchange = locationHashChanged;

Jeśli przeglądarka nie obsługuje oldURLi newURLmożesz to powiązać w następujący sposób:

//let this snippet run before your hashChange event binding code
if( !window.HashChangeEvent )( function() {
    let lastURL = document.URL;
    window.addEventListener( "hashchange", function( event ) {
        Object.defineProperty( event, "oldURL", { enumerable: true, configurable: true, value: lastURL } );
        Object.defineProperty( event, "newURL", { enumerable: true, configurable: true, value: document.URL } );
        lastURL = document.URL;
    } );
} () );

8
Zdarzenia Hashchange są wysyłane tylko wtedy, gdy adres URL ma część zaczynającą się od symbolu „#”, zwykle używanego w przypadku linków zakotwiczenia. W przeciwnym razie nie odpali.
Fredrik_Macrobond,

1
window.addEventListener ("hashchange", funcRef, false);
Bishoy Hanna

7

Czy próbowałeś wcześniej usunąć? To zdarzenie jest wywoływane bezpośrednio przed odpowiedzią strony na żądanie nawigacji i powinno obejmować modyfikację atrybutu href.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
    <HTML>
    <HEAD>
    <TITLE></TITLE>
    <META NAME="Generator" CONTENT="TextPad 4.6">
    <META NAME="Author" CONTENT="?">
    <META NAME="Keywords" CONTENT="?">
    <META NAME="Description" CONTENT="?">
    </HEAD>

         <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
            <script type="text/javascript">
            $(document).ready(function(){
                $(window).unload(
                        function(event) {
                            alert("navigating");
                        }
                );
                $("#theButton").click(
                    function(event){
                        alert("Starting navigation");
                        window.location.href = "http://www.bbc.co.uk";
                    }
                );

            });
            </script>


    <BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#FF0000" VLINK="#800000" ALINK="#FF00FF" BACKGROUND="?">

        <button id="theButton">Click to navigate</button>

        <a href="http://www.google.co.uk"> Google</a>
    </BODY>
    </HTML>

Uważaj jednak, aby zdarzenie było uruchamiane za każdym razem , gdy opuścisz stronę, czy to z powodu skryptu, czy kliknięcia linku przez kogoś. Twoim prawdziwym wyzwaniem jest wykrycie różnych przyczyn wystrzelenia zdarzenia. (Jeśli jest to ważne dla Twojej logiki)


Nie jestem pewien, czy to zadziała, ponieważ często zmiany skrótu nie wymagają ponownego załadowania strony.
samandmoore

Potrzebuję również dostępu do nowego DOMU, zaktualizowałem pytanie, aby to wyjaśnić.
Johan Dahlin,

OK - nie rozważałem hashsytuacji - pomyślę o tym. Jeśli chodzi o możliwość uzyskania dostępu do nowego DOM, to nie masz szczęścia (jeśli chodzi o dostęp do niego z modułu obsługi zdarzeń), ponieważ zdarzenie zostanie uruchomione przed załadowaniem nowego DOM. Możesz być w stanie włączyć logikę do zdarzenia onload nowej strony, ale możesz mieć te same problemy w odniesieniu do określenia, czy musisz wykonywać logikę dla każdego ładowania tej strony. Czy możesz podać więcej szczegółów na temat tego, co próbujesz osiągnąć, w tym przepływu stron?
belugabob

Właśnie próbowałem uzyskać dostęp do location.href wewnątrz modułu obsługi zdarzeń onbeforeunload i pokazuje oryginalny adres URL, a nie docelowy adres URL.
CMCDragonkai,

Beforeunload może anulować wyładowanie strony, więc zdarzenie unload jest lepsze, jeśli nie chcesz anulować nawigacji.
inf3rno


2

Istnieją 2 sposoby zmiany location.href. Możesz albo napisać location.href = "y.html", który przeładuje stronę, albo możesz użyć API historii, które nie przeładowuje strony. Ostatnio dużo eksperymentowałem z pierwszym.

Jeśli otworzysz okno podrzędne i przechwycisz ładowanie strony podrzędnej z okna nadrzędnego, różne przeglądarki zachowują się bardzo różnie. Jedyne, co jest wspólne, jest to, że usuwają stary dokument i dodają nowy, więc na przykład dodanie programów obsługi zdarzeń readystatechange lub load do starego dokumentu nie daje żadnego efektu. Większość przeglądarek usuwa również programy obsługi zdarzeń z obiektu window, jedynym wyjątkiem jest Firefox. W przeglądarce Chrome z Karma runner i Firefox możesz przechwycić nowy dokument w pliku readyState ładowania, jeśli go używaszunload + next tick. Możesz więc dodać na przykład procedurę obsługi zdarzeń ładowania lub procedurę obsługi zdarzeń readystatechange lub po prostu zarejestrować, że przeglądarka ładuje stronę z nowym identyfikatorem URI. W przeglądarce Chrome z testowaniem ręcznym (prawdopodobnie również GreaseMonkey) oraz w Operze, PhantomJS, IE10, IE11 nie można przechwycić nowego dokumentu w stanie ładowania. W tych przeglądarkach unload + next tickwywołuje oddzwanianie kilkaset ms później niż następuje załadowanie strony. Opóźnienie wynosi zwykle 100 do 300 ms, ale opera simetime powoduje 750 ms opóźnienia dla następnego taktu, co jest przerażające. Więc jeśli chcesz uzyskać spójny wynik we wszystkich przeglądarkach, robisz to, co chcesz po zdarzeniu ładowania, ale nie ma gwarancji, że lokalizacja nie zostanie wcześniej zastąpiona.

var uuid = "win." + Math.random();
var timeOrigin = new Date();
var win = window.open("about:blank", uuid, "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes");


var callBacks = [];
var uglyHax = function (){
    var done = function (){
        uglyHax();
        callBacks.forEach(function (cb){
            cb();
        });
    };
    win.addEventListener("unload", function unloadListener(){
        win.removeEventListener("unload", unloadListener); // Firefox remembers, other browsers don't
        setTimeout(function (){
            // IE10, IE11, Opera, PhantomJS, Chrome has a complete new document at this point
            // Chrome on Karma, Firefox has a loading new document at this point
            win.document.readyState; // IE10 and IE11 sometimes fails if I don't access it twice, idk. how or why
            if (win.document.readyState === "complete")
                done();
            else
                win.addEventListener("load", function (){
                    setTimeout(done, 0);
                });
        }, 0);
    });
};
uglyHax();


callBacks.push(function (){
    console.log("cb", win.location.href, win.document.readyState);
    if (win.location.href !== "http://localhost:4444/y.html")
        win.location.href = "http://localhost:4444/y.html";
    else
        console.log("done");
});
win.location.href = "http://localhost:4444/x.html";

Jeśli uruchamiasz swój skrypt tylko w przeglądarce Firefox, możesz użyć uproszczonej wersji i przechwycić dokument w stanie ładowania, więc na przykład skrypt na załadowanej stronie nie może odejść przed zarejestrowaniem zmiany URI:

var uuid = "win." + Math.random();
var timeOrigin = new Date();
var win = window.open("about:blank", uuid, "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes");


var callBacks = [];
win.addEventListener("unload", function unloadListener(){
    setTimeout(function (){
        callBacks.forEach(function (cb){
            cb();
        });
    }, 0);
});


callBacks.push(function (){
    console.log("cb", win.location.href, win.document.readyState);
    // be aware that the page is in loading readyState, 
    // so if you rewrite the location here, the actual page will be never loaded, just the new one
    if (win.location.href !== "http://localhost:4444/y.html")
        win.location.href = "http://localhost:4444/y.html";
    else
        console.log("done");
});
win.location.href = "http://localhost:4444/x.html";

Jeśli mówimy o aplikacjach jednostronicowych, które zmieniają część hash identyfikatora URI lub używają API historii, możesz użyć hashchangeodpowiednio popstatezdarzeń i zdarzeń okna. Mogą one przechwytywać, nawet jeśli poruszasz się w historii wstecz i do przodu, dopóki nie pozostaniesz na tej samej stronie. Dokument się nie zmienia, a strona nie jest ponownie ładowana.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.