本文原發在個人博客。javascript
前段時間電影《瘋狂動物城》上映了,而後個人知乎首頁就被它刷屏了。雖然我對這部電影沒有任何意見,但做爲一個還沒去電影院看過的人來講,每看到相關問題一次都是無情的劇透,因而我毅然屏蔽了「瘋狂動物園」這個話題。本覺得問題解決了,可是接下來我又被迫看到這個問題:html
問題上添加的五個話題無一命中,我又被劇透了一臉。算了,既然知乎的屏蔽規則靠不住,那就本身動手吧。這樣個人Chrome瀏覽器擴展——ZhihuFilter就誕生了,點擊這裏查看Github上的項目。java
其實擴展的功能很簡單,當打開知乎首頁後,擴展會依次檢查你的屏蔽關鍵詞列表是否出如今了某一個答案中,若是出現了,就會把這個答案隱藏,取而代之的是提示信息和一個展開答案的按鈕。效果以下圖所示:jquery
你能夠點擊圖中的按鈕來查看答案,以後能夠選擇再次隱藏或展開。git
當你安裝了擴展後,會在地址欄的右側顯示出圖標github
點擊圖標後,將會出現設置屏蔽詞的界面web
你能夠在這個頁面中設置你想屏蔽的詞語。正則表達式
關於Chrome擴展開發的內容,能夠查看Google的官方文檔或者是這個教程。chrome
一個應用(擴展)實際上是壓縮在一塊兒的一組文件,包括HTML,CSS,Javascript腳本,圖片文件,還有其它任何須要的文件。json
開發擴展的時候,必不可少的是一個manifest.json
文件,這是一個配置文件,會告訴Chrome你的擴展中包含了哪些內容。manifest.json
中包含擴展的名字、版本及各類資源文件(如圖標、顯示頁面等)的連接。
Browser_action
和page_action
是擴展的兩種類型,它們很類似,主要的區別在於browser_action
能夠應用於全部的頁面,而page_action
只能在你設定的幾個特定網站內使用。個人擴展是專門用於知乎網站的,所以選擇了page_action
來處理。
按照Google的文檔描述,二者還有一個區別:browser_action
的圖標顯示在地址欄的外部,page_action
的圖標顯示在地址欄內部。可是,在這裏的討論中,彷佛新版本的Chrome瀏覽器中已經將二者都顯示在了地址欄的外側,不過page_action
的圖標只有在打開特定的網站時纔不會顯示爲灰色。
在manifest.json
文件中進行以下設置:
"page_action": { "default_icon": "images/icon.png", "default_title": "知乎屏蔽擴展" },
在Manifest中指定background域可使擴展常駐後臺。background能夠包含三種屬性,分別是
scripts
、page
和persistent
。
我在擴展中只用到了scripts
:
"background": { "scripts": ["js/jquery-2.2.1.js","js/background.js"] },
這樣就會自動生成一個包含了列出的腳本文件的後臺頁面。
在個人background.js
文件中有以下內容:
// 當網址改變的時候,判斷當前的頁面是不是知乎 // 若是是的話,就顯示出圖標,並設置它的彈出頁面 chrome.tabs.onUpdated.addListener(function(id, info, tab){ if (tab.url.toLowerCase().indexOf("zhihu.com") > -1){ chrome.pageAction.show(id); chrome.pageAction.setPopup({ tabId: id, popup: 'popup.html' }); } });
background.js
文件中還有一個用於和content_script
進行通訊的監聽器。
Content scripts是在Web頁面內運行的javascript腳本。經過使用標準的DOM,它們能夠獲取瀏覽器所訪問頁面的詳細信息,並能夠修改這些信息。
在manifest.json
文件中進行設置:
"content_scripts": [ { "matches": ["*://*.zhihu.com/*"], "js": ["js/jquery-2.2.1.js", "js/content_script.js"] }
在打開匹配的網站時,列出的js文件會被注入頁面,這樣就能夠得到瀏覽器所訪問的web頁面的詳細信息,並能夠對頁面作出修改。雖然content script和頁面共享了DOM結構,但不能訪問該頁面或其它content script中定義的函數和變量,這樣就避免了相同的函數或變量名稱的干擾。
個人擴展的主要功能就是在content_script.js
中完成的,在該腳本中經過對頁面的DOM進行操做來實現功能。
查看一下知乎首頁的源代碼,全部的答案內容是放在一個id=js-home-feed-list
的div中的,結構大體以下:
<!--這裏省略了不少內容,只是一個大體示意--> <div id="js-home-feed-list"> <div class="feed-item"> <!--這裏省略一些題目的id,url等信息--> <div class="avatar"></div> <!--頭像--> <div class="feed-main"> <!--除了頭像外的其它部分--> <div class="source"></div> <div class="content"> <!--答案內容及按鈕等部分--> </div> </div> </div> <div class="feed-item"></div> <div class="feed-item"></div> </div>
而在答案部分中,又分爲摘要和完整的答案。
<div class="zm-item-answer-detail"> … <div class="zm-item-rich-text"> <!--內容部分--> <div class="zm-editable-content"></div> <!--點擊顯示所有後,這一部分才顯示--> <textarea class="content hidden"></div> <!--包括了所有答案內容但不顯示--> <div class="zh-summary summary clearfix"></div> <!--顯示摘要--> </div> </div>
咱們能夠獲取上面的節點內容,來肯定是否須要屏蔽這個答案。最簡單的實現方法就是查找關鍵詞是否在節點的outerHTML
中出現,若是出現了就給.feed-main
加上一個名爲hidden
的class。
原來的答案被隱藏了以後,須要在這個地方換上點新的內容,我在這裏建立了一個div,內部有一個p元素用於顯示信息,及一個button用於切換答案的狀態。
// 建立用於替換的div var $div = $('<div class="block-info"></div>'); $div.append($('<p>這裏有一個被屏蔽的答案<span></span></p>')); $div.append($('<button class="block-btn">手賤一下</button>'));
還須要在按鈕上綁定點擊事件,p元素內顯示的信息也會根據實際進行修改。
在個人擴展中,我是將須要屏蔽的關鍵詞存放在了localStorage
中。關鍵詞保存在localStorage中的keywords
鍵下,是一個簡單的由逗號分隔開的字符串。
要訪問同一個localStorage對象,頁面必須來自同一個域名(子域名無效),使用同一種協議,在同一個端口上。
因爲content script注入到了知乎頁面,而localStorage存放在擴展的域下,要在content script中得到關鍵詞的值,就必須用到頁面中的通訊。
Chrome提供了4個有關擴展頁面間相互通訊的接口,分別是runtime.sendMessage
、runtime.onMessage
、runtime.connect
和runtime.onConnect
,
這裏用到了前兩個。
content_script.js
// 從擴展的localStorage中得到存儲的關鍵詞 chrome.runtime.sendMessage({method: "getKeywords"}, function (response) { str = response.keywords; keywords = str !== '' ? str.split(',') : []; });
對應的background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { if (request.method == "getKeywords") sendResponse({keywords: localStorage['keywords']}); else sendResponse({}); });
這樣就能夠在content script中使用localStorage的值了。
參考資料:擴展頁面中的通訊
每當當知乎頁面加載了更多的答案時,咱們須要再進行處理。那麼如何檢測頁面中加載了更多內容,這裏須要用到MutationObserver
對象。MutationObserver
提供了檢測頁面中的DOM變化的方法。
MutationObserver
的使用方法:
首先,建立一個mutationObserver的實例
var observer = new MutationObserver(callback)
這裏須要一個回調函數做爲構造器的參數。
callback函數接受一個參數,全部被記錄到的DOM變化將會組合成一個數組,做爲參數傳給回調函數進行處理。
mutationObserver對象有幾個方法,這裏只用到observe方法。
observe方法接收兩個參數:
void observe( Node target, MutationObserverInit options );
第一個參數指的是你要觀察哪個節點的DOM變化。第二個參數是一個選項參數,
var option = { 'childList': true, // 觀察子元素 'subtree': true, // 觀察目標節點的後代元素 'attributes': false // 不觀察目標節點屬性的變化 };
這裏只設置了三個選項,其他屬性能夠看MDN文檔。
觀察到的每個變化都是一個MutationRecord對象,它有許多屬性,好比:
type,記錄變化的類型
addedNodes,由增長的節點組成的NodeList
removeNodes,由刪除的節點組成的NodeList
由於addedNodes是一個NodeList,因此能夠在它上面應用jQuery的$(), $(mutation.addedNodes)
。
其它的屬性能夠見MDN文檔。
全部的MutationRecord會被放進一個list中,做爲參數傳給上面的callback函數。經過對MutationRecord對象的屬性進行檢測,若是新增的節點裏出現了class=feed-main
的元素,則表明加載了新的答案,須要再一次運行程序。
在個人設想中,擴展能夠提供一些選項,好比正則表達式的支持,再好比除了首頁外,在答案頁面是否也須要提供屏蔽功能。這些選項會在以後逐步加入。
因爲Chrome的設置,不可以安裝Web Store中沒有的程序。而發佈擴展須要先付$5,我沒有信用卡,也就暫時沒有發佈擴展。若是想嘗試一下擴展的話,能夠直接下載Github中的代碼到本地。在Chrome瀏覽器的菜單中選擇 More tools -> Extensions,進入擴展頁面後,勾選右上角的Developer mode,選擇Load unpacked extension,選擇擴展文件夾便可。
歡迎使用後提出建議。