Подія при зміні window.location.href


89

Я пишу сценарій Greasemonkey для сайту, який в якийсь момент модифікується location.href.

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

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


Відповіді:


91

поп-станція :

Подія 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
Але чи почнеться вогонь перед завантаженням вмісту?
user2782001

4
непридатний, якщо не має жодного контролю над фрагментом кодуpushState
Натан Гай

2
Це не завжди спрацьовує, воно покладається на звільнення випускної події. Наприклад, навігація в 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 спрацьовує лише тоді, коли користувач використовує кнопки вперед / назад і змінює хеш лише для зміни хешу. Якщо у вас немає контролю над кодом, який спричиняє зміну місцезнаходження, вам доведеться опитувати 🤷‍♀️
Ріко

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
Події хеш-обміну надсилаються лише у тому випадку, якщо у вашій URL-адресі є частина, що починається із символу '#', що зазвичай використовується для прив'язки посилань. Інакше це не спрацює.
Fredrik_Macrobond

1
window.addEventListener ("hashchange", funcRef, false);
Бішоя Ганна

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, я оновив питання, щоб це пояснити.
Йохан Далін,

Добре - не розглядав hashситуацію - подумаю над цим. Що стосується можливості отримати доступ до нового DOM, то вам не пощастило (що стосується доступу до цього з обробника подій), оскільки подія запускатиметься до завантаження нового DOM. Можливо, ви зможете включити логіку у подію завантаження нової сторінки, але у вас можуть виникнути однакові проблеми щодо визначення, чи потрібно виконувати логіку для кожного завантаження цієї сторінки. Чи можете ви надати більше детальної інформації про те, чого ви намагаєтесь досягти, включаючи потік сторінки?
belugabob

Я просто спробував отримати доступ до location.href всередині обробника події onbeforeunload, і він показує вихідну URL-адресу, а не цільову.
CMCDragonkai

Beforeunload може скасувати розвантаження сторінки, тому подія вивантаження є кращою, якщо ви не хочете скасовувати навігацію.
inf3rno


2

Ну, є 2 способи змінити location.href. Або ви можете написати location.href = "y.html", що перезавантажує сторінку, або можете використовувати API історії, який не перезавантажує сторінку. Нещодавно я експериментував із першими.

Якщо ви відкриваєте дочірнє вікно і фіксуєте навантаження дочірньої сторінки з батьківського вікна, то різні браузери поводяться дуже по-різному. Єдине загальне, що вони видаляють старий документ і додають новий, тому, наприклад, додавання готового обміну або завантаження обробників подій до старого документа не має ніякого ефекту. Більшість браузерів також видаляють обробники подій з об'єкта вікна, єдиним винятком є ​​Firefox. У Chrome із Karma runner та у Firefox ви можете захопити новий документ у завантажувальний readyState, якщо використовуєтеunload + next tick. Таким чином, ви можете додати, наприклад, обробник події завантаження або обробник події readystatechange або просто записати, що браузер завантажує сторінку з новим URI. У Chrome із ручним тестуванням (можливо, GreaseMonkey теж) та в Opera, PhantomJS, IE10, IE11 ви не можете захопити новий документ у стані завантаження. У цих браузерах unload + next tickвиклик зворотного виклику відбувається на кілька сотень мсек. Пізніше, ніж запускається подія завантаження сторінки. Затримка, як правило, становить 100-300 мсек, але час опери оперує 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.