Evento cuando cambia window.location.href


83

Estoy escribiendo un script de Greasemonkey para un sitio que en algún momento modifica location.href.

¿Cómo puedo obtener un evento (vía window.addEventListenero algo similar) cuando window.location.hrefcambia en una página? También necesito acceso al DOM del documento que apunta a la URL nueva / modificada.

He visto otras soluciones que implican tiempos de espera y encuestas, pero me gustaría evitar eso si es posible.


Respuestas:


87

evento popstate :

El evento popstate se activa cuando cambia la entrada activa del historial. [...] El evento popstate solo se activa al realizar una acción del navegador, como hacer clic en el botón Atrás (o llamar a history.back () en JavaScript)

Por lo tanto, escuchar el popstateevento y enviar un popstateevento cuando se usa history.pushState()debería ser suficiente para tomar medidas sobre el hrefcambio:

window.addEventListener('popstate', listener);

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

2
Pero, ¿se disparará el popstate antes de que se cargue el contenido?
user2782001

4
inutilizable si no tienes ningún control sobre el fragmento de código quepushState
Nathan Gouy

2
Esto no siempre funciona, depende de que se active un evento popstate. Por ejemplo, navegar dentro de YouTube no se activará, solo si presiona hacia atrás o hacia adelante en el historial
TrySpace

53

Utilizo este script en mi extensión "Grab Any Media" y funciona bien ( como en el caso de 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);

};

De alguna manera, me gusta este enfoque ... se siente un poco RxJs-ish :)
Guntram

La única solución que podría haber funcionado para youtube: [Solo informe] Se negó a crear un trabajador de ' youtube.com/sw.js ' porque viola la siguiente directiva de Política de seguridad de contenido: "worker-src 'none'".
Simon Meusel

MutationObserver

1
Trabajó desde el primer momento sin problemas para mi caso de uso, ¡Dios los bendiga! Es molesto que aún no haya un evento nativo para esto (popstate no funcionó para mí), ¡pero un día! Sin window.addEventListener("load", () => {})embargo, usaría en lugar de window.onload :)
Sanchit Batra

esto funciona, detecta history.pushState incluso en el contexto de extensión
YangombiUmpakati

29

No puede evitar el sondeo, no hay ningún evento para el cambio de href.

De todos modos, el uso de intervalos es bastante ligero si no se excede. Verificar el href cada 50 ms aproximadamente no tendrá ningún efecto significativo en el rendimiento si eso le preocupa.


3
@AlexanderMills: afortunadamente existen estas nuevas soluciones de popstatey hashchange.
serv-inc

Esto no es verdad.
inf3rno

@ MartinZvarík Incluso en 2010 fue posible utilizar el evento de descarga más una tarea micro o macro para cargar el nuevo documento.
inf3rno

3
Desafortunadamente, esta respuesta sigue siendo correcta en 2020. popstate solo se activa cuando el usuario usa los botones de avance / retroceso y hashchange solo se activa para cambios de hash. A menos que tenga control sobre el código que está causando que cambie la ubicación, debe sondear 🤷‍♀️
Rico Kahler

12

Hay un onhashchangeevento predeterminado que puede utilizar.

Documentado AQUÍ

Y se puede usar así:

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

window.onhashchange = locationHashChanged;

Si el navegador no es compatible oldURLy newURLpuede vincularlo así:

//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
Los eventos Hashchange solo se envían si su URL tiene una parte que comienza con un símbolo '#', que normalmente se usa para enlaces de anclaje. De lo contrario, no disparará.
Fredrik_Macrobond

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

7

¿Has probado antes de descargar? Este evento se activa inmediatamente antes de que la página responda a una solicitud de navegación, y esto debería incluir la modificación del 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>

Sin embargo, tenga en cuenta que su evento se activará cada vez que navegue fuera de la página, ya sea por el script o porque alguien hace clic en un enlace. Tu verdadero desafío, es detectar las diferentes razones por las que el evento está siendo despedido. (Si esto es importante para tu lógica)


No estoy seguro de si esto funcionará porque a menudo los cambios de hash no implican una recarga de página.
samandmoore

También necesito acceso al nuevo DOM, actualicé la pregunta para aclarar eso.
Johan Dahlin

Está bien, no consideró la hashsituación, pensará en eso. En cuanto a poder acceder al nuevo DOM, entonces no tiene suerte (en lo que respecta a acceder a esto desde un controlador de eventos) ya que el evento se activará antes de que se cargue el nuevo DOM. Es posible que pueda incorporar la lógica en el evento de carga de la nueva página, pero puede tener los mismos problemas con respecto a identificar si necesita llevar a cabo la lógica para cada carga de esa página. ¿Puede proporcionar más detalles sobre lo que está tratando de lograr, incluido el flujo de la página?
belugabob

Intenté acceder a location.href dentro del controlador de eventos onbeforeunload, y muestra la URL original, no la URL de destino.
CMCDragonkai

Beforeunload puede cancelar la descarga de la página, por lo que el evento de descarga es mejor si no desea cancelar la navegación.
inf3rno


2

Bueno, hay 2 formas de cambiar el location.href. O puede escribir location.href = "y.html", lo que recarga la página o puede usar la API de historial que no recarga la página. Experimenté mucho con el primero recientemente.

Si abre una ventana secundaria y captura la carga de la página secundaria desde la ventana principal, los diferentes navegadores se comportan de manera muy diferente. Lo único que es común, que eliminan el documento antiguo y agregan uno nuevo, por ejemplo, agregar readystatechange o cargar controladores de eventos al documento anterior no tiene ningún efecto. La mayoría de los navegadores también eliminan los controladores de eventos del objeto de la ventana, la única excepción es Firefox. En Chrome con Karma Runner y en Firefox puedes capturar el nuevo documento en el estado de carga readyState si usasunload + next tick. Por lo tanto, puede agregar, por ejemplo, un controlador de eventos de carga o un controlador de eventos readystatechange o simplemente registrar que el navegador está cargando una página con un nuevo URI. En Chrome con pruebas manuales (probablemente GreaseMonkey también) y en Opera, PhantomJS, IE10, IE11 no puede capturar el nuevo documento en el estado de carga. En esos navegadores, las unload + next tickllamadas a la devolución de llamada unos cientos de ms después de que se activa el evento de carga de la página. El retraso es típicamente de 100 a 300 ms, pero opera simetime hace un retraso de 750 ms para el siguiente tic, lo cual da miedo. Entonces, si desea un resultado consistente en todos los navegadores, haga lo que quiera después del evento de carga, pero no hay garantía de que la ubicación no se anule antes de eso.

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

Si ejecuta su script solo en Firefox, entonces puede usar una versión simplificada y capturar el documento en un estado de carga, por ejemplo, un script en la página cargada no puede navegar antes de registrar el cambio de 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";

Si estamos hablando de aplicaciones de una sola página que cambian la parte hash de la URI, o usan la API del historial, entonces puede usar hashchangelos popstateeventos y de la ventana respectivamente. Esos pueden capturar incluso si se mueve en el historial hacia atrás y hacia adelante hasta que permanezca en la misma página. El documento no cambia por esos y la página realmente no se vuelve a cargar.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.