【问题标题】:Event when window.location.href changeswindow.location.href 更改时的事件
【发布时间】:2011-04-01 03:04:27
【问题描述】:

我正在为一个网站编写一个 Greasemonkey 脚本,该脚本在某些时候会修改 location.href

当页面上的window.location.href 发生变化时,如何获取事件(通过window.addEventListener 或类似的东西)?我还需要访问指向新/修改后 url 的文档的 DOM。

我见过其他涉及超时和轮询的解决方案,但我希望尽可能避免这种情况。

【问题讨论】:

标签: javascript dom greasemonkey dom-events


【解决方案1】:

我在我的扩展“抓取任何媒体”中使用这个脚本并且工作正常(就像 youtube 案例

var oldHref = document.location.href;

window.onload = function() {
    var bodyList = document.querySelector("body")

    var 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
  • 开箱即用,对我的用例没有任何问题,上帝保佑!令人讨厌的是,还没有本机事件(popstate 对我不起作用),但是有一天!我会使用 window.addEventListener("load", () => {}) 而不是 window.onload :)
  • 伙计,这太棒了。迄今为止我见过的最聪明的解决方案
【解决方案2】:

popstate event

当活动的历史条目改变时会触发 popstate 事件。 [...] popstate 事件仅通过执行浏览器操作触发,例如单击后退按钮(或在 JavaScript 中调用 history.back())

因此,在使用 history.pushState() 时监听 popstate 事件并发送 popstate 事件应该足以对 href 更改采取行动:

window.addEventListener('popstate', listener);

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

【讨论】:

  • 但是popstate会在内容加载之前触发吗?
  • 如果无法控制pushState的那段代码,则无法使用
  • 这并不总是有效,它依赖于要触发的 popstate 事件。例如,在 youtube 中导航它不会触发,只有当您在历史记录中按后退或前进时才会触发
  • 这假设您有可见性或知道是否调用了 pushState.. 它不会在任何地方都有效,就像状态之上的 cmets 一样
【解决方案3】:

你无法避免轮询,没有任何用于更改href的事件。

如果你不过分的话,使用间隔还是很轻的。如果您担心的话,每 50 毫秒左右检查一次 href 不会对性能产生任何显着影响。

【讨论】:

  • @AlexanderMills:幸运的是popstatehashchange 的这些新解决方案。
  • 这不是真的。
  • @MartinZvarík 即使在 2010 年,也可以使用 unload 事件以及微任务或宏任务来加载新文档。
  • 不幸的是,这个答案在 2020 年仍然是正确的。popstate only fires when the user uses the forward/back buttons 和 hashchange 仅在哈希更改时触发。除非您可以控制导致位置更改的代码,否则您必须轮询?‍♀️
【解决方案4】:

您可以使用默认的onhashchange 事件。

Documented HERE

并且可以这样使用:

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

window.onhashchange = locationHashChanged;

如果浏览器不支持oldURLnewURL,可以这样绑定:

//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;
    } );
} () );

【讨论】:

  • Hashchange 事件仅在您的 URL 包含以“#”符号开头的部分时发送,通常用于锚链接。否则它不会触发。
  • window.addEventListener("hashchange", funcRef, false);
【解决方案5】:

通过Jquery,试一试

$(window).on('beforeunload', function () {
    //your code goes here on location change 
});

通过使用 javascript

window.addEventListener("beforeunload", function (event) {
   //your code goes here on location change 
});

参考文档:https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload

【讨论】:

  • 试试吧,火狐上不行。
【解决方案6】:

你试过之前卸载吗? 此事件在页面响应导航请求之前立即触发,这应该包括对 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>

但请注意,您的事件将触发每当您离开页面时,无论这是因为脚本,还是有人点击链接。 您真正的挑战是检测触发事件的不同原因。 (如果这对您的逻辑很重要)

【讨论】:

  • 我不确定这是否可行,因为哈希更改通常不涉及页面重新加载。
  • 我也需要访问新的 DOM,我更新了问题以澄清这一点。
  • 好的——没有考虑hash的情况——会考虑那个。至于能够访问新的 DOM,那么您就不走运了(就从事件处理程序访问它而言),因为事件将在新 DOM 加载之前触发。您可能能够将逻辑合并到新页面的 onload 事件中,但是在确定是否需要为该页面的每次加载执行逻辑时,您可能会遇到相同的问题。您能否提供更多关于您想要实现的目标的详细信息,包括页面流程?
  • 我刚刚尝试访问 onbeforeunload 事件处理程序中的 location.href,它显示的是原始 url,而不是目标 url。
  • Beforeunload 可以取消页面的卸载,所以如果不想取消导航,最好是unload事件。
【解决方案7】:

有两种方法可以更改location.href。您可以编写location.href = "y.html",它会重新加载页面,也可以使用不重新加载页面的历史API。我最近对第一个进行了很多实验。

如果您打开一个子窗口并从父窗口捕获子页面的负载,那么不同的浏览器的行为会非常不同。唯一常见的是,他们删除旧文档并添加新文档,因此例如将 readystatechange 或 load 事件处理程序添加到旧文档没有任何效果。大多数浏览器也会从窗口对象中删除事件处理程序,唯一的例外是 Firefox。在带有 Karma runner 的 Chrome 和 Firefox 中,如果您使用 unload + next tick,您可以在 loading readyState 中捕获新文档。因此,您可以添加例如 load 事件处理程序或 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,那么您可以分别使用窗口的 hashchangepopstate 事件。即使您在历史记录中前后移动,直到您停留在同一页面上,这些也可以捕获。文档不会被这些更改,页面也没有真正重新加载。

【讨论】:

    【解决方案8】:

    试试这个脚本,它可以让您在 URL 更改时运行代码(无需页面加载,如单页应用程序):

    var previousUrl = '';
    var observer = new MutationObserver(function(mutations) {
      if (location.href !== previousUrl) {
          previousUrl = location.href;
          console.log(`URL changed to ${location.href}`);
        }
    });
    

    【讨论】:

      【解决方案9】:

      根据“Leonardo Ciaccio”的回答,修改后的代码如下: 即移除 for 循环,如果移除了则重新分配 Body Element

      window.addEventListener("load", function() {
          let oldHref = document.location.href,
              bodyDOM = document.querySelector("body");
          const observer = new MutationObserver(function(mutations) {
              if (oldHref != document.location.href) {
                  oldHref = document.location.href;
                  console.log("the location href is changed!");
                  window.requestAnimationFrame(function() {
                      let tmp = document.querySelector("body");
                      if(tmp != bodyDOM) {
                          bodyDOM = tmp;
                          observer.observe(bodyDOM, config);
                      }
                  })
              }
          });
          const config = {
              childList: true,
              subtree: true
          };
          observer.observe(bodyDOM, config);
      }, true);
      

      【讨论】:

      • 纯代码答案并不是特别有用。请添加一些关于此代码如何解决问题的说明。
      猜你喜欢
      • 2011-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多