1. 程式人生 > >對於單頁應用中如何監聽 URL 變化的思考

對於單頁應用中如何監聽 URL 變化的思考

nta 使用 賬戶 hash 延遲 css 也有 asc log

周末開發了一個在 GitHub 中給 repo 增加自定義備註的 chrome 擴展。

開發這個擴展的原因是我在 GitHub 中所 star 的項目實在太多了(截止目前 671 個),有的項目過個幾天回來看就忘了為什麽 star 了,有的輪子想找的時候發現忘記叫什麽了,這麽多一個個找實在浪費時間。於是我一直在想有這麽個工具,可以自定義對 GitHub 中的項目進行備註,然後可以根據備註進行搜索,於是便有了這個擴展。

如需安裝體驗請點擊 GitHub Remarks 進行安裝,源碼移步 github-remarks(沒啥參考價值)。

當然本文並不是講這個擴展的制作過程,而是在這過程中碰到的一個問題。

以我的 GitHub 賬戶舉例,在 https://github.com/hanzichi?tab=stars 頁面,我需要插入一些 dom,使得頁面如下:

技術分享圖片

乍一想,似乎很簡單,在這個頁面插入 content.js 就行,在 manifest.json 中增加配置:

{
  "matches": ["*://github.com/.*tabs=stars"],
  "js": ["content-scripts/repoDetail.js"],
  "css": [],
  "run_at"
: "document_end" }

但是問題來了,直接打開頁面 https://github.com/hanzichi?tab=stars 並沒有問題,但是如果從 https://github.com/hanzichi 點擊跳轉到 https://github.com/hanzichi?tab=stars,因為 GitHub 用了 pjax 技術,content.js 其實應該加載在頁面 https://github.com/hanzichi 的,但是那個頁面並沒有參照的 dom 結構(好把我們需要的 dom 插入)。所以問題似乎就轉為了,在 pjax 頁面,如何能夠監聽到頁面 url 的變化

首先一個很簡單的方案是,弄個定時器循環監聽,但是太耗費性能了,pass

因為頁面是 pjax 跳轉,所以 hashchange 這樣的事件自然也用不上;而 pushstate 事件是監聽瀏覽器前進後退的,所以也並不符合場景

有個想法很好,重寫 pushState 事件(代碼來自 How to detect when HTML5‘s history.pushState() is called?):

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    }
})(window.history);

但是因為 chrome 擴展和源代碼之間並不共享作用域,所以重寫的 history.pushState 方法在原來頁面其實並沒有改變,所以也沒什麽卵用

然後從 chrome API 入手,發現有個 chrome API 可以檢測當前 url 改變的 tab(參考 Chrome extension WebNavigation listener for hash change):

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
  if (changeInfo.url) {
      console.log('Tab %d got new URL: %s', tabId, changeInfo.url)
  }
})

這段代碼是在 background.js 中,然後一旦偵測到 URL 變化,background.js 和 content.js 通信告知即可。

但是,但是,也有問題,url 一改變確實能監聽到,但是 chrome 擴展需要在相應 dom ready 後才能作用(才能在之前 dom 的基礎上插入新的 dom),你無法檢測到什麽時候 dom 已經準備就緒了,所以在這裏加個定時器延遲作用是可行的,時間需要自己估算下

這個方法不是很優雅,最後想到,pjax 的過程中,dom 肯定有變化,所以監聽 dom 變化是否可行?答案是可以的:

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
  let url = location.href 
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

observer.observe(document.getElementById('js-pjax-container'), {
  childList: true
})

$(document).ready(() => {
  let url = location.href 
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

寫到最後,其實並不是監聽了 URL,而是監聽了 dom 的變化。

最後的最後,意外在 so 上找到 這個答案 似乎就是對應我的問題,大概看了下,除了我舉例的幾個方案外,其實我遺漏了最簡單的一個方案,既然頁面使用了 pjax,那麽直接監聽 pjax:complete 事件即可:

$(document).on('pjax:complete', function() {
  let url = location.href 
  let p = /.*\/\/github.com\/.*\?tab=stars.*/

  if (p.test(url)) {
    initStarsPage()
  }
})

突然讓人感覺,有時候 "真相" 往往總是那麽簡單。

對於單頁應用中如何監聽 URL 變化的思考