前幾天掘金那篇什麼挑戰前端的文章很火,但是幾十的贊幾百的評論,說明這篇文章不是火在質量而是火在爭議。客觀的來講裡面那道題還是不錯的,能幫助我們理解js的一些機制。作者評論裡面多次提到前端版塊文章質量的問題,我想說的是不止前端版塊其他版塊也一樣,任何社群的都不可能保證所有的文章都是精品,只是掘金前端版塊文章數量比較多,這個問題比較明顯,但是每個讀者水平不一樣,你覺得這篇講的東西沒什麼用,但可能對於別人就很有幫助。
所以就如同評論下面很多人在說希望掘金出一個拉黑遮蔽功能,可以拉黑作者,遮蔽文章。這個功能確實可以滿足很多人的篩選文章的需求,但是催這個功能也不是一天兩天了,官方可能有自己的計劃一直沒有提上開發日程。我之前其實沒這個需求,頂多就是不太想看面試類的文章,但是那篇文章出來之後,我發現我還是有拉黑需求的,起碼那篇文章不應該出現在我的文章推薦列表中。雖然知道官方日後肯定會出這個功能,但是遠水解不了近渴,不如當下先自己開發一個外掛來實現這個功能。
需求說明
- 將不喜歡的作者加入黑名單,在首頁和搜尋文章時不再顯示黑名單中作者的文章。
- 將不喜歡的關鍵字加入標題黑名單,在首頁和搜尋文章時不再顯示標題中包含關鍵字的文章。
- 在文章詳情頁面可以隨時拉黑作者。
- 可以對黑名單列表進行管理(新增和刪除)。
成品展示
特此說明,作者拉黑只是為了展示功能,而且為了效果特意選擇了專欄比較多的高等級作者,對兩位上鏡的作者並無冒犯之意。
作者遮蔽
標題關鍵字遮蔽
方案思路
實現思路其實非常簡單,首先要明確的一點是掘金的文章列表是後臺請求回的資料,將來官方實現這個功能的時候可以後端直接返回過濾後的結果,也可以前端拿到資料時進行過濾處理再展示。而對於我們外部外掛來說沒有這個能力,只能通過將已經生成好的頁面元素進行隱藏來達到我們的目的。
控制檯輸入程式碼版
我們先來一個最簡單的實現,首先我們開啟chrome的除錯皮膚,看一下掘進首頁文章列表的html結構:
如圖所示所有的文章以li
標籤的形式,放在class
為entry-list
的ul
中,那麼我們現在要做的是選中entry-list
下的所有li
標籤並查詢裡面的內容,如果存在css
關鍵字就隱藏掉這個li
標籤。
//$$是大部分瀏覽器除錯控制檯都支援的API,等價於document.querySelectorAll,平時開發不能用。
$$('.entry-list>li').forEach(item => {
//innerText是一個節點及其後代的“渲染”文字內容,判斷裡面是否包含css,不區分大小寫。
if (/css/i.test(item.innerText)) {
//符合要求的元素隱藏
item.hidden = true;
}
});
複製程式碼
在控制檯輸入這段程式碼,你就會發現文章列表裡面的包含css關鍵字的條目(這個不光是標題)都被隱藏掉了。
但是滾動條下滑後載入的文章還是會顯示的,如果想隱藏只能在控制檯再執行一遍這個程式碼。
油猴指令碼版
我們要做的是一個完整的外掛,而不能像上面那樣人工手動在控制檯輸入程式碼,雖然你寫一個完整的外掛程式碼控制檯跑也是可以,功能效果其實是一樣的,但終究不是長遠之計。
首先我們可以把這個功能做成一個瀏覽器外掛,比如根據Chrome的規範和API建立專案開發,最後打包成crx格式的檔案,Chrome瀏覽器就可以本地安裝這個外掛,或者花點錢成為谷歌開發者釋出到谷歌應用商店。
瀏覽器外掛的好處是,可以呼叫更多瀏覽器級的API,使用瀏覽器為原生外掛提供的相關功能,但是對於我們只針對一個網站提供的小功能來說,有些殺雞用牛刀了,而且這個外掛也只有Chrome能用,如果其他瀏覽器想用還要再開發,比如FireFox就使用的是xpi格式的擴充套件。
一種更好的方式就是使用Tampermonkey。
Tampermonkey(國內習慣稱其為油猴)是一個瀏覽器外掛,是最為流行的使用者指令碼管理器,而且在 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox都有對應的版本,也就是說我們寫的指令碼可以通過它跑在各個瀏覽器上。而我們今天就是通過油猴指令碼的形式來實現我們的功能。
對瀏覽器外掛開發更感興趣的朋友推薦這篇文章:【乾貨】Chrome外掛(擴充套件)開發全攻略
方案實現
安裝Tampermonkey
Tampermonkey的安裝說簡單也簡單,說不簡單也不簡單,簡單的是Tampermonkey被收錄在各個瀏覽器的官方外掛市場,很輕易的就可以搜尋下載,而且不僅限於前面提到的那些瀏覽器,一些移動端瀏覽器也是支援的,還有雖然Tampermonkey官網沒寫,但是一些國產瀏覽器外掛市場也是有的,比如360極速瀏覽器,搜狗瀏覽器。不簡單的原因嘛,就是有些瀏覽器的外掛市場我們們不好訪問。
如果瀏覽器外掛市場確實沒有,只要是chromium核心的,通常都支援crx本地安裝。
安裝完成後我們可以在瀏覽器上點選油猴外掛圖示,可以在管理皮膚中檢視,開啟,刪除,管理我們的指令碼,可以在獲取新指令碼中找到幾個指令碼市場的網站,直接下載別人寫好的指令碼。
建立指令碼檔案
我們要自己開發指令碼的話,選擇新增新的指令碼就可以開啟一個指令碼編寫頁面,但是建議還是在自己編輯器裡寫,有提示和格式化更方便。
建立後的指令碼預設有一些程式碼如下,就是頭部像是註釋一樣的東西,這裡要用到的簡單說明一下,更多內容可以參考官方文件。
// ==UserScript==
// @name 掘金文章黑名單 <-外掛名,會顯示在管理皮膚裡
// @namespace https://github.com/hoc2019/blackList <-指令碼名稱空間
// @version 0.1 <-版本號
// @description 掘金文章黑名單過濾指令碼 <-描述
// @author wangzy <-作者
// @match https://juejin.im/* <-只有匹配到的網站才會使用該指令碼
// @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js <-引入指令碼,這裡用了jQuery
// @grant GM_addStyle <-給網站注入css樣式的方法
// ==/UserScript==
(function() {
'use strict';
// Your code here...
})();
複製程式碼
文章列表過濾 - MutationObserver觀察dom節點變化
文章列表的過濾隱藏和上面控制檯輸入程式碼版原理差不多,重點是觸發隱藏操作的時機。由於資料非同步請求,過早的話文章列表dom節點還沒有被建立,過濾操作就會失敗,而過晚的話就會肉眼可見列表的消失。
一個簡單粗暴的方式就是寫一個輪詢,不斷的去檢測文章列表dom節點是否存在,存在則表示文章載入完成,可以進行過濾操作了。同理滾動到底部載入文章,也可通過輪詢檢測文章列表dom節點數量是否改變來觸發過濾操作(當然也可以檢測滾輪事件)。但是輪詢本身就是一個在效能和體驗上找平衡的方法,輪詢間隔短了判斷就準確,體驗就好,但是會消耗很多效能,反之則效能好,體驗就差。
所以這裡我們使用MutationObserver,MutationObserver可以用來監聽DOM節點的變化,是舊的Mutation Events功能的替代品,詳細內容可以參考MDN的文件。
大概的使用方式如下:
const container = $('#juejin');
const config = {
attributes: false, // 檢測節點屬性變化,這裡用不到,為減少不必要的觸發這裡不用開啟
childList: true, // 檢測子節點新增和刪除
subtree: true // 檢測包含後代節點
};
const mutationCallback = mutationsList => {
for (let mutation of mutationsList) {
const type = mutation.type;
const addedNodes = mutation.addedNodes; //增加節點陣列
// 會根據上面的的設定觸發相應事件,這裡可以判斷觸發的事件型別
switch (type) {
case 'childList':
//因為我們是要判斷列表載入,只用處理節點增加時的事件即可
if (addedNodes.length > 0) {
list = $('.entry-list');
if (list.children().length) {
//停止觀察
loadObserver.disconnect();
//過濾操作
filter(list.children());
//建立滾動後載入觀察
createNodeListener(list[0], config, updateLoad);
}
}
break;
}
}
};
//建立首次載入觀察
const loadObserver = createNodeListener(container[0], config, handleLoad);
//定義一個建立觀察者的工廠函式,方便建立觀察者。
function createNodeListener(node, config, mutationCallback) {
const observer = new MutationObserver(mutationCallback);
observer.observe(node, config);
return observer;
}
複製程式碼
由於是每次文章列表節點發生變化就去處理,所以響應很及時,不太會出現條目先顯示再消失的情況,而且是被動觸發的,並不會像輪詢一樣,後臺不斷在獲取查詢dom節點資訊。
這裡可能會有疑問為什麼中間要重新建立一個新的觀察,因為一開始的觀察是基於一個非常頂層的容器(id為juejin的div節點,一開始只有它存在),文章列表之外的一些其他DOM節點變化也會觸發事件,所以當我們拿到文章列表的容器後(class為entry-list的ul),就停止對頂層容器的觀察改為只觀察文章列表容器,這樣我們就可以精確的只響應文章列表節點的變動。
通過MutationObserver建構函式建立完觀察者後不會立即啟動觀察,需要通過觀察者者呼叫observe方法,需要注意的是observe方法的第一個引數必須是DOM Node (可能是一個Element) ,通過jQuery獲取的節點要轉化為原生dom節點,例如$('#juejin')[0]這樣。
黑名單資料的儲存 - localStorage資料存取
黑名單資料儲存在localStorage,一個即使頁面關閉也可以長期保留資料的方式,具體內容可以檢視MDN的文件。
使用方式很簡單,但要注意的是localStorage儲存的只能是字串,如果想儲存陣列和物件需要做一下序列化處理,轉成json字串。
//儲存
const authorBlackList = ['小四', '小五'];
localStorage.setItem('authorBlackList', JSON.stringify(authorBlackList));
//讀取
const authorBlackList = JSON.parse(localStorage.getItem('authorBlackList'));
複製程式碼
儲存後的資料可以開啟瀏覽器除錯的Application皮膚檢視,由於是本地儲存瀏覽器清空localStorage就會導致資料清除,跨瀏覽器也是不能共用資料的,服務端儲存的話需要成本,這事還是交給官方來幹吧。
黑名單管理側邊欄
這個也比較好實現,寫好html,通過jQuery建立節點插入頁面就可以了,css樣式的話要通過油猴指令碼提供的方法GM_addStyle插入,前面有提到過。
大概就是下面這個樣子:
//css樣式
const myScriptStyle ='.black-sidebar{background:#000}'
GM_addStyle(myScriptStyle);
//html結構
const sidebarHtml = '<div class="black-sidebar"><ul><ul></div>';
$('body').append($(sidebarHtml));
複製程式碼
可能唯一注意的點就是側邊欄中黑名單列表是後期可以動態新增的,所以點選事件不能直接通過jquery的click方法繫結在生成的li
標籤上,而是要通過on方法繫結在父級ul
上,就和事件委託一個道理。不過這些都是jQuery的用法注意點,不知道新入行的朋友還會不會用到。
不同頁面側邊欄資料同步 - visibilitychange事件監聽
黑名單管理側邊欄是一開始就生成的,雖然當前頁的操作會更新到側邊欄,但是通常在瀏覽過程中我們是開啟多個頁面的,那我們要如何保持多個頁面側邊欄的資料同步呢?顯然跨瀏覽器標籤頁操作dom是不現實的,但是我們可以去監聽一個標籤頁的切換,當該標籤頁為顯示狀態時,重新獲取一下資料並更新列表。
我們通過visibilitychange事件來監聽,該事件詳情可以參考MDN文件。
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
// 更新側邊欄資料
updateSidebarList();
if (pathname === 'post') {
//如果是詳情頁,更新拉黑按鈕狀態
updateBtnState();
}
}
});
複製程式碼
效果如下:
上面document.visibilityState是隻讀屬性表示標籤頁狀態,visible表示顯示,還有hidden表示隱藏。所以通過該方法還可以檢測到標籤頁的隱藏,移動端瀏覽器切換到後臺s,媒體聲音還在繼續播放的問題,可以通過這個事件手動暫停解決。
文章詳情頁拉黑按鈕
這個也很簡單,不過在做的時候碰到一個有意思的地方,本來想著建立一個和上面分享按鈕一樣的元素,新增同樣的類名就可以了,但是掘金的樣式裡類選擇器裡還帶了data-v-xxx資料(vue防止css樣式汙染使用的scoped特性),於是乾脆直接克隆了一個分享按鈕的節點,再對內容作了修改。
最後
這個外掛基本功能已經完成,但是由於開發的時候目的就是為了自用,沒有考慮相容性,也沒有各種情況下的測試,只是自己跑通能用就行.
其實還是很多點可以優化,比如現在新增刪除黑名單後要頁面重新整理才能生效,一些dom操作還可以減少,程式碼可以進一步抽象封裝,物件導向等等。所以這篇文章的目的並不是推廣這個外掛,而是簡單的分享一下指令碼的開發流程,和裡面解決某些問題的思路,能引起某些人關於指令碼開發的興趣就足夠了,這也是這個系列文章的目的。
這個指令碼的程式碼我會放到Github,有需求的可以用用試試,指令碼也已經放到GreasyFork指令碼市場上了,可以直接搜尋掘金文章黑名單下載使用,但是後續還沒有什麼更新計劃,bug隨緣修復,感興趣的可以參與共同維護,在官方出這功能之前,我們們自己動手,豐衣足食。