【實踐思考】自己開發一個掘金黑名單功能外掛

wangzy2019發表於2019-08-11

前幾天掘金那篇什麼挑戰前端的文章很火,但是幾十的贊幾百的評論,說明這篇文章不是火在質量而是火在爭議。客觀的來講裡面那道題還是不錯的,能幫助我們理解js的一些機制。作者評論裡面多次提到前端版塊文章質量的問題,我想說的是不止前端版塊其他版塊也一樣,任何社群的都不可能保證所有的文章都是精品,只是掘金前端版塊文章數量比較多,這個問題比較明顯,但是每個讀者水平不一樣,你覺得這篇講的東西沒什麼用,但可能對於別人就很有幫助。

所以就如同評論下面很多人在說希望掘金出一個拉黑遮蔽功能,可以拉黑作者,遮蔽文章。這個功能確實可以滿足很多人的篩選文章的需求,但是催這個功能也不是一天兩天了,官方可能有自己的計劃一直沒有提上開發日程。我之前其實沒這個需求,頂多就是不太想看面試類的文章,但是那篇文章出來之後,我發現我還是有拉黑需求的,起碼那篇文章不應該出現在我的文章推薦列表中。雖然知道官方日後肯定會出這個功能,但是遠水解不了近渴,不如當下先自己開發一個外掛來實現這個功能。

需求說明

  1. 將不喜歡的作者加入黑名單,在首頁和搜尋文章時不再顯示黑名單中作者的文章。
  2. 將不喜歡的關鍵字加入標題黑名單,在首頁和搜尋文章時不再顯示標題中包含關鍵字的文章。
  3. 在文章詳情頁面可以隨時拉黑作者。
  4. 可以對黑名單列表進行管理(新增和刪除)。

成品展示

特此說明,作者拉黑只是為了展示功能,而且為了效果特意選擇了專欄比較多的高等級作者,對兩位上鏡的作者並無冒犯之意。

作者遮蔽

【實踐思考】自己開發一個掘金黑名單功能外掛

標題關鍵字遮蔽

【實踐思考】自己開發一個掘金黑名單功能外掛

方案思路

實現思路其實非常簡單,首先要明確的一點是掘金的文章列表是後臺請求回的資料,將來官方實現這個功能的時候可以後端直接返回過濾後的結果,也可以前端拿到資料時進行過濾處理再展示。而對於我們外部外掛來說沒有這個能力,只能通過將已經生成好的頁面元素進行隱藏來達到我們的目的。

控制檯輸入程式碼版

我們先來一個最簡單的實現,首先我們開啟chrome的除錯皮膚,看一下掘進首頁文章列表的html結構:

【實踐思考】自己開發一個掘金黑名單功能外掛

如圖所示所有的文章以li標籤的形式,放在classentry-listul中,那麼我們現在要做的是選中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隨緣修復,感興趣的可以參與共同維護,在官方出這功能之前,我們們自己動手,豐衣足食。

指令碼倉庫地址 文章備份倉庫地址

相關文章