Sự kiện khi window.location.href thay đổi


82

Tôi đang viết một tập lệnh Greasemonkey cho một trang web mà tại một số điểm sẽ sửa đổi location.href.

Làm cách nào tôi có thể nhận được một sự kiện (thông qua window.addEventListenerhoặc một cái gì đó tương tự) khi các window.location.hrefthay đổi trên một trang? Tôi cũng cần quyền truy cập vào DOM của tài liệu trỏ đến url mới / đã sửa đổi.

Tôi đã thấy các giải pháp khác liên quan đến thời gian chờ và thăm dò ý kiến, nhưng tôi muốn tránh điều đó nếu có thể.


Câu trả lời:


87

sự kiện popstate :

Sự kiện cửa sổ bật lên được kích hoạt khi mục lịch sử hoạt động thay đổi. [...] Sự kiện popstate chỉ được kích hoạt bằng cách thực hiện một hành động trên trình duyệt, chẳng hạn như nhấp vào nút quay lại (hoặc gọi history.back () trong JavaScript)

Vì vậy, việc lắng nghe popstatesự kiện và gửi popstatesự kiện khi sử dụng history.pushState()là đủ để thực hiện hành động hrefthay đổi:

window.addEventListener('popstate', listener);

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

2
Nhưng liệu popstate có hoạt động trước khi nội dung được tải không?
user2782001 23/03

4
không sử dụng được nếu không có bất kỳ quyền kiểm soát nào đối với đoạn mã đópushState
Nathan Gouy

2
Điều này không phải lúc nào cũng hoạt động, nó dựa vào một sự kiện phổ biến để bị sa thải. Ví dụ: điều hướng trong youtube nó sẽ không kích hoạt, chỉ khi bạn nhấn trở lại hoặc chuyển tiếp trong lịch sử
TrySpace

52

Tôi sử dụng tập lệnh này trong tiện ích mở rộng "Lấy mọi phương tiện" và hoạt động tốt ( như trường hợp trên 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);

};

Bằng cách nào đó, tôi giống như cách tiếp cận này ... cảm thấy một chút RxJs-ish :)
Guntram

Giải pháp duy nhất có thể hiệu quả với youtube: [Chỉ báo cáo] Đã từ chối tạo công nhân từ ' youtube.com/sw.js ' vì vi phạm chỉ thị Chính sách bảo mật nội dung sau: "worker-src 'none'".
Simon Meusel

MutationObserver

1
Làm việc nhanh chóng mà không có rắc rối cho trường hợp sử dụng của tôi, Chúa phù hộ! Thật khó chịu khi chưa có sự kiện gốc nào cho điều này (popstate không hoạt động với tôi) nhưng một ngày! Tôi sẽ sử dụng window.addEventListener("load", () => {})thay vì window.onload mặc dù :)
Sanchit Batra

điều này hoạt động, phát hiện history.pushState ngay cả trong ngữ cảnh mở rộng
YangombiUmpakati

29

Bạn không thể tránh bỏ phiếu, không có bất kỳ sự kiện nào để thay đổi href.

Việc sử dụng các khoảng thời gian dù sao cũng khá nhẹ nhàng nếu bạn không đi quá đà. Kiểm tra href mỗi 50ms hoặc lâu hơn sẽ không có bất kỳ ảnh hưởng đáng kể nào đến hiệu suất nếu bạn lo lắng về điều đó.


3
@AlexanderMills: rất may là có những giải pháp mới này của popstatehashchange.
serv-inc

Đây không phải là sự thật.
inf3rno

@ MartinZvarík Thậm chí vào năm 2010, có thể sử dụng sự kiện dỡ hàng cùng với một tác vụ vi mô hoặc vĩ mô để tải tài liệu mới.
inf3rno

3
Thật không may, câu trả lời này vẫn đúng vào năm 2020. popstate chỉ kích hoạt khi người dùng sử dụng các nút chuyển tiếp / quay lại và hashchange chỉ kích hoạt cho các thay đổi hash. Trừ khi bạn có quyền kiểm soát mã khiến vị trí thay đổi, bạn phải thăm dò ý kiến ​​🤷‍♀️
Rico Kahler

12

Có một onhashchangesự kiện mặc định mà bạn có thể sử dụng.

Đã ghi tài liệu TẠI ĐÂY

Và có thể được sử dụng như thế này:

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

window.onhashchange = locationHashChanged;

Nếu trình duyệt không hỗ trợ oldURLnewURLbạn có thể liên kết nó như thế này:

//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
Sự kiện Hashchange chỉ được gửi nếu URL của bạn có một phần bắt đầu bằng ký hiệu '#', thường được sử dụng cho các liên kết cố định. Nếu không nó sẽ không bắn.
Fredrik_Macrobond

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

7

Bạn đã thử bao giờ chưa? Sự kiện này kích hoạt ngay lập tức trước khi trang phản hồi một yêu cầu điều hướng và điều này sẽ bao gồm việc sửa đổi 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>

Tuy nhiên, hãy lưu ý rằng sự kiện của bạn sẽ kích hoạt bất cứ khi nào bạn điều hướng khỏi trang, cho dù điều này là do tập lệnh hay ai đó nhấp vào liên kết. Thách thức thực sự của bạn là phát hiện các lý do khác nhau khiến sự kiện bị sa thải. (Nếu điều này quan trọng đối với logic của bạn)


Tôi không chắc liệu điều này có hiệu quả hay không vì thường những thay đổi về hàm băm không liên quan đến việc tải lại trang.
samandmoore

Tôi cũng cần quyền truy cập vào DOM mới, tôi đã cập nhật câu hỏi để làm rõ điều đó.
Johan Dahlin

OK - không xem xét hashtình huống - sẽ nghĩ về điều đó. Đối với việc có thể truy cập DOM mới, thì bạn không gặp may (liên quan đến việc tích lũy điều này từ trình xử lý sự kiện) vì sự kiện sẽ kích hoạt trước khi DOM mới được tải. Bạn có thể kết hợp logic vào sự kiện onload của trang mới, nhưng bạn có thể gặp vấn đề tương tự liên quan đến việc xác định xem bạn có cần thực hiện logic cho mỗi lần tải trang đó hay không. Bạn có thể cung cấp thêm một số chi tiết về những gì bạn đang cố gắng đạt được, bao gồm cả luồng trang không?
belugabob

Tôi vừa thử truy cập vào location.href bên trong trình xử lý sự kiện onbeforeunload và nó hiển thị url gốc chứ không phải url đích.
CMCDragonkai

Beforeunload có thể hủy bỏ việc dỡ trang, vì vậy sự kiện dỡ hàng sẽ tốt hơn, nếu bạn không muốn hủy điều hướng.
inf3rno


2

Vâng, có 2 cách để thay đổi location.href. Bạn có thể viết location.href = "y.html", thao tác tải lại trang hoặc có thể sử dụng API lịch sử không tải lại trang. Tôi đã thử nghiệm với cái đầu tiên rất nhiều gần đây.

Nếu bạn mở cửa sổ con và nắm bắt tải trang con từ cửa sổ mẹ, thì các trình duyệt khác nhau hoạt động rất khác nhau. Điểm chung duy nhất là họ loại bỏ tài liệu cũ và thêm một tài liệu mới, vì vậy, ví dụ thêm trình xử lý sự kiện sẵn sàng hoặc tải vào tài liệu cũ không có bất kỳ tác dụng nào. Hầu hết các trình duyệt cũng xóa các trình xử lý sự kiện khỏi đối tượng cửa sổ, ngoại lệ duy nhất là Firefox. Trong Chrome với Á hậu Karma và trong Firefox, bạn có thể chụp tài liệu mới trong Trạng thái sẵn sàng tải nếu bạn sử dụngunload + next tick. Vì vậy, bạn có thể thêm ví dụ: một trình xử lý sự kiện tải hoặc một trình xử lý sự kiện readystatechange hoặc chỉ cần ghi nhật ký rằng trình duyệt đang tải một trang với một URI mới. Trong Chrome với kiểm tra thủ công (có thể cả GreaseMonkey) và trong Opera, PhantomJS, IE10, IE11, bạn không thể chụp tài liệu mới ở trạng thái tải. Trong các trình duyệt đó, unload + next ticklệnh gọi lại chậm hơn vài trăm mili giây so với sự kiện tải của trang kích hoạt. Độ trễ thường là 100 đến 300 msec, nhưng opera simetime tạo ra độ trễ 750 msec cho lần đánh dấu tiếp theo, điều này thật đáng sợ. Vì vậy, nếu bạn muốn có một kết quả nhất quán trong tất cả các trình duyệt, thì bạn sẽ làm những gì bạn muốn sau sự kiện tải, nhưng không có gì đảm bảo rằng vị trí sẽ không bị ghi đè trước đó.

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

Nếu bạn chỉ chạy tập lệnh của mình trong Firefox, thì bạn có thể sử dụng phiên bản đơn giản hóa và chụp tài liệu ở trạng thái tải, vì vậy, ví dụ: một tập lệnh trên trang đã tải không thể điều hướng đi trước khi bạn ghi thay đổi 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";

Nếu chúng ta đang nói về các ứng dụng trang đơn thay đổi phần băm của URI hoặc sử dụng API lịch sử, thì bạn có thể sử dụng hashchangecác popstatesự kiện và sự kiện của cửa sổ tương ứng. Những thứ đó có thể ghi lại ngay cả khi bạn di chuyển trong lịch sử tới lui cho đến khi bạn ở trên cùng một trang. Tài liệu không thay đổi bởi những điều đó và trang không thực sự được tải lại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.