Событие при изменении window.location.href


88

Я пишу сценарий Greasemonkey для сайта, который в какой-то момент изменяется location.href.

Как я могу получить событие (через window.addEventListenerили что-то подобное) при window.location.hrefизменении на странице? Мне также нужен доступ к DOM документа, указывающего на новый / измененный URL-адрес.

Я видел другие решения, которые включают тайм-ауты и опрос, но я бы хотел избежать этого, если это возможно.


Ответы:


90

событие popstate :

Событие popstate запускается при изменении активной записи в истории. [...] Событие popstate запускается только при выполнении действия браузера, такого как щелчок по кнопке возврата (или вызов history.back () в JavaScript)

Итак, прослушивания popstateсобытия и отправки popstateсобытия при использовании history.pushState()должно быть достаточно, чтобы принять меры по hrefизменению:

window.addEventListener('popstate', listener);

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

2
Но сработает ли popstate до загрузки контента?
user2782001

4
непригоден для использования, если у вас нет контроля над фрагментом кода, которыйpushState
Натан Гуи

2
Это не всегда работает, это зависит от срабатывания события popstate. Например, при навигации по YouTube он не сработает, только если вы нажмете назад или вперед в истории
TrySpace

57

Я использую этот скрипт в своем расширении "Grab Any Media" и отлично работаю ( как в случае с 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);

};

Почему-то мне нравится этот подход ... кажется немного RxJs-ish :)
Гунтрам

Единственное решение, которое могло сработать для youtube: [Только отчет] Отказано в создании воркера из youtube.com/sw.js , потому что это нарушает следующую директиву Политики безопасности контента: worker-src 'none' ».
Саймон Мейсель

MutationObserver

1
Для моего варианта использования все заработало без проблем, дай бог! Раздражает, что для этого пока нет родного события (popstate у меня не работал), но однажды! Я бы использовал window.addEventListener("load", () => {})вместо window.onload :)
Санчит Батра

это работает, обнаруживает history.pushState даже в контексте расширения
YangombiUmpakati

29

Вы не можете избежать опроса, нет никакого события для изменения href.

В любом случае использовать интервалы довольно легко, если вы не переборщите. Проверка href примерно каждые 50 мс не окажет значительного влияния на производительность, если вы беспокоитесь об этом.


3
@AlexanderMills: к счастью, есть эти новые решения popstateи hashchange.
serv-inc

1
Это неправда.
inf3rno

@ MartinZvarík Даже в 2010 году можно было использовать событие выгрузки, а также микро- или макро-задачу для загрузки нового документа.
inf3rno

3
К сожалению, этот ответ по-прежнему верен в 2020 году. Popstate срабатывает только тогда, когда пользователь использует кнопки вперед / назад, а hashchange срабатывает только для изменений хеша. Если у вас нет контроля над кодом, который вызывает изменение местоположения, вам нужно провести опрос 🤷‍♀️
Рико Калер

14

Есть onhashchangeсобытие по умолчанию, которое вы можете использовать.

Документировано ЗДЕСЬ

И можно использовать так:

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

window.onhashchange = locationHashChanged;

Если браузер не поддерживает oldURLи newURLвы можете привязать его так:

//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
События Hashchange отправляются только в том случае, если ваш URL-адрес имеет часть, начинающуюся с символа '#', обычно используемого для якорных ссылок. Иначе не сработает.
Fredrik_Macrobond 05

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

7

Вы раньше пробовали разгрузить? Это событие возникает непосредственно перед тем, как страница ответит на запрос навигации, и это должно включать изменение 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>

Однако помните , что ваше событие будет срабатывать всякий раз, когда вы уходите со страницы, будь то из-за сценария или из-за того, что кто-то щелкает ссылку. Ваша настоящая задача - определить разные причины увольнения мероприятия. (Если это важно для вашей логики)


Я не уверен, что это сработает, потому что часто изменения хэша не связаны с перезагрузкой страницы.
samandmoore

Мне также нужен доступ к новому DOM, я обновил вопрос, чтобы прояснить это.
Johan Dahlin

Ладно - не hashучел - подумаю. Что касается возможности доступа к новой DOM, то вам не повезло (что касается доступа к ней из обработчика событий), поскольку событие сработает до загрузки новой DOM. Вы могли бы включить логику в событие onload новой страницы, но у вас могут возникнуть те же проблемы с определением, нужно ли вам выполнять логику для каждой загрузки этой страницы. Можете ли вы предоставить более подробную информацию о том, чего вы пытаетесь достичь, включая поток страниц?
belugabob

Я просто попытался получить доступ к location.href внутри обработчика события onbeforeunload, и он показывает исходный URL-адрес, а не целевой URL-адрес.
CMCDragonkai

Beforeunload может отменить выгрузку страницы, поэтому событие unload лучше, если вы не хотите отменять навигацию.
inf3rno


2

Есть 2 способа изменить файл location.href. Либо ты можешь написатьlocation.href = "y.html" , который перезагружает страницу, либо использовать API истории, который не перезагружает страницу. Я недавно много экспериментировал с первым.

Если вы открываете дочернее окно и фиксируете загрузку дочерней страницы из родительского окна, то разные браузеры ведут себя по-разному. Единственное, что является общим: они удаляют старый документ и добавляют новый, поэтому, например, добавление обработчиков событий readystatechange или load к старому документу не имеет никакого эффекта. Большинство браузеров также удаляют обработчики событий из объекта окна, единственным исключением является Firefox. В Chrome с бегуном Karma и в Firefox вы можете захватить новый документ в готовом состоянии загрузки, если вы используетеunload + next tick. Таким образом, вы можете добавить, например, обработчик событий загрузки или обработчик событий readystatechange или просто зарегистрировать, что браузер загружает страницу с новым URI. В Chrome с ручным тестированием (возможно, GreaseMonkey тоже) и в Opera, PhantomJS, IE10, IE11 вы не можете захватить новый документ в состоянии загрузки. В этих браузерах unload + next tickобратный вызов вызывается на несколько сотен миллисекунд позже, чем срабатывает событие загрузки страницы. Задержка обычно составляет от 100 до 300 мс, но Opera simetime делает задержку в 750 мс для следующего тика, что пугает. Поэтому, если вы хотите получить согласованный результат во всех браузерах, вы делаете то, что хотите, после события загрузки, но нет гарантии, что местоположение не будет переопределено до этого.

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

Если вы запускаете свой скрипт только в Firefox, вы можете использовать упрощенную версию и захватить документ в состоянии загрузки, поэтому, например, скрипт на загруженной странице не может уйти до того, как вы зарегистрируете изменение 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";

Если мы говорим об одностраничных приложениях, которые изменяют хеш-часть URI или используют API истории, то вы можете использовать hashchangeи popstateсобытия окна соответственно. Их можно снимать, даже если вы перемещаетесь по истории назад и вперед, пока не останетесь на той же странице. Документ ими не меняется и страница не перезагружается.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.