摘要:咦,為什么不做一個插件用來管理呢每次同時打開過多的選項卡時,被擠壓的標題總是讓我分不清哪個是哪個,查看起來十分不便。即數據中的屬性,因此關閉選項卡的功能實現起來也沒有問題。
1. 前言
繼上周第一次開發Chrome插件github-star-trend之后,我就一直尋思有什么現實問題可以用插件來解決呢?正當我在瀏覽器中搜索尋找靈感時,打開的眾多tab選項卡令我靈光一閃。
咦,為什么不做一個插件用來管理tab呢?每次同時打開過多的tab選項卡時,被擠壓的標題總是讓我分不清哪個是哪個,查看起來十分不便。于是乎,經過一個周末下午的折騰,我倒騰出這么個東西(gif圖可能有點大,請耐心等待...):
2. 準備工作按照慣例,正式進入主題之前讓我們來先了解點預備知識。默默打開Chrome插件的官方文檔,直奔我們的Tabs。可以看到它為我們提供了很多方法,而且竟然還有executeScript,這個可以說權限非常大了,不過跟我們這次的需求沒啥關系。。。
2.1 query由于我們的需求是管理tab選項卡,所以首先肯定得獲取所有的tab信息。掃了一遍Methods,最相關的就是方法query:
Gets all tabs that have the specified properties, or all tabs if no properties are specified.
正如官方介紹,該方法可以根據指定條件返回相應的tabs;且當不指定屬性時,可以獲得所有的tabs。這恰好滿足我們的需求,按照API指示,我在callback中嘗試打印出了拿到的tabs對象:
chrome.tabs.query({}, tabs => console.log(tabs));
[ { "active": true, "audible": false, "autoDiscardable": true, "discarded": false, "favIconUrl": "https://static.clewm.net/static/images/favicon.ico", "height": 916, "highlighted": true, "id": 25, "incognito": false, "index": 0, "mutedInfo": {"muted":false}, "pinned": true, "selected": true, "status": "complete", "title": "草料文本二維碼生成器", "url": "https://cli.im/text?bb032d49e2b5fec215701da8be6326bb", "width": 1629, "windowId": 23 }, ... { "active": true, "audible": false, "autoDiscardable": true, "discarded": false, "favIconUrl": "https://www.google.com/images/icons/product/chrome-32.png", "height": 948, "highlighted": true, "id": 417, "incognito": false, "index": 0, "mutedInfo": {"muted": false}, "pinned": false, "selected": true, "status": "complete", "title": "chrome.tabs - Google Chrome", "url": "https://developers.chrome.com/extensions/tabs#method-query", "width": 1629, "windowId": 812 } ]
仔細觀察不難發現,兩個tab的windowId不同。這是由于我在本地同時打開了兩個Chrome窗口,而這兩個tab恰好在兩個不同的窗口內,所以正好符合預期。
另外id,index, highlighted,favIconUrl,title等字段信息在后文中也起到非常重要的作用,相關的釋義都可以在這里查看。
在構思Chrome插件UI時,為了突出當前窗口中的當前tab,我們就必須從上述數據中找出這個tab。由于每個窗口中都有一個tab是highlighted的,所以我們無法直接確定哪個tab是當前窗口的。不過,我們可以這樣:
chrome.tabs.query( {active: true, currentWindow: true}, tabs => console.log(tabs[0]) );
根據文檔,通過指定active和currentWindow這兩個屬性為true,我們就能順利拿到當前窗口的當前tab。然后再根據tab的windowId和highlighted進行匹配,我們就能從tabs數組中定位出哪個才是真正的當前tab了。
2.2 highlight根據上面所述,我們已經可以拿到所有的tabs信息以及確定出哪個tab是當前窗口的當前tab,所以我們可以根據這些數據構建出一個列表。而接下來要做的就是,當用戶點擊其中某一項時,瀏覽器就能切換到所對應的tab選項卡。帶著這個需求,再次翻閱文檔找到了highlight:
Highlights the given tabs and focuses on the first of group. Will appear to do nothing if the specified tab is currently active.
chrome.tabs.highlight({windowId, tabs});
根據該API的指示,它需要的是windowId和tab的index,而這些信息都在每個tab實體中可以拿到。不過這里有一個坑需要注意:那就是如果在當前窗口切換到另一個窗口的tab時,雖然另一個窗口的tab得以切換,但是Chrome窗口仍聚焦于當前窗口。所以需要用以下的方法,令另外的那個窗口得到聚焦:
chrome.windows.update(windowId, {focused: true});2.3 remove
為了增強插件的實用性,我們可以在tabs列表中加入刪除指定tab選項卡的功能。而在翻閱文檔之后,可以確定remove可以實現我們的需求。
Closes one or more tabs.
chrome.tabs.remove(tabId);
tabId即tab數據中的id屬性,因此關閉選項卡的功能實現起來也沒有問題。
3. 開工不同于插件github-star-trend,這次復雜度更高,涉及到更多的交互操作。為此,我們引入react,antd和webpack,不過整體開發起來還是比較容易的,更多的可能還是在于Chrome插件提供的API熟練度。
3.1 manifest.json{ "permissions": [ "tabs" ], "content_security_policy": "script-src "self" "unsafe-eval"; object-src "self"", "browser_action": { "default_icon": { "16": "./icons/logo_16.png", "32": "./icons/logo_32.png", "48": "./icons/logo_48.png" }, "default_title": "Tab Killer", "default_popup": "./popup.html" } }
由于這次開發的插件跟tabs相關,所以我們需要在permissions字段中申請tabs權限。
由于webpack在dev模式下打包會用到eval,Chrome瀏覽器出于安全策略會報錯,因此需要設置content_security_policy使其忽略(如果是prod模式打的包,就不需要設置)。
本次插件的交互是點擊按鈕彈出一個浮層,所以需要設置browser_action屬性,而其default_popup字段正是我們接下來要開發的頁面。
3.2 App.js該文件是我們的核心文件之一,主要負責tabs數據的獲取和處理等維護工作。
根據API文檔所示,獲取tabs數據是一個異步操作,我們在其回調函數中才能拿到。這也意味著我們的應用一開始應該是處于一個LOADING的狀態,拿到數據之后成為OK狀態,另外再考慮到異常情況(例如無數據或出錯),我們可以將其定義為EXCEPTION狀態。
class App extends React.PureComponent { state = { tabsData: [], status: STATUS.LOADING } componentDidMount() { this.getTabsData(); } getTabsData() { Promise.all([ this.getAllTabs(), this.getCurrentTab(), Helper.waitFor(300), ]).then(([allTabs, currentTab]) => { const tabsData = Helper.convertTabsData(allTabs, currentTab); if(tabsData.length > 0) { this.setState({tabsData, status: STATUS.OK}); } else { this.setState({tabsData: [], status: STATUS.EXCEPTION}); } }).catch(err => { this.setState({tabsData: [], status: STATUS.EXCEPTION}); console.log("get tabs data failed, the error is:", err.message); }); } getAllTabs = () => new Promise(resolve => chrome.tabs.query({}, tabs => resolve(tabs))) getCurrentTab = () => new Promise(resolve => chrome.tabs.query({active: true, currentWindow: true}, tabs => resolve(tabs[0]))) render() { const {status, tabsData} = this.state; return (); } } const Helper = { waitFor(timeout) { return new Promise(resolve => { setTimeout(resolve, timeout); }); }, convertTabsData() {} }
思路很簡單,就是在didMount的時候獲取tabs數據,不過我們在這里用到Promise.all來控制異步操作。
由于獲取tabs數據這一操作是異步的,不同電腦,不同狀態,不同tab數量時該操作的耗時都可能不同,所以為了更好的用戶體驗,我們可以在一開始用antd的Spin組件來充當占位符。需要注意的是,如果獲取tabs數據非常快,Loading動畫會有一閃而過的感覺,并不十分友好。因此我們用個300ms的promise搭配Promise.all使用,可以保證至少300ms的Loading動畫。
接下來就是拿到tabs數據之后的convert工作。
Chrome提供的API獲取到的數據是一個扁平的數組,不同窗口內的tab也被混在同一個數組內。我們更希望能按窗口進行分組,這樣在瀏覽和查找時對用戶更直觀,操作更方便,用戶體驗更好。所以我們需要對tabsData進行一次轉換:
convertTabsData(allTabs = [], currentTab = {}) { // 過濾非法數據 if(!(allTabs.length > 0 && currentTab.windowId !== undefined)) { return []; } // 按windowId進行分組歸類 const hash = Object.create(null); for(const tab of allTabs) { if(!hash[tab.windowId]) { hash[tab.windowId] = []; } hash[tab.windowId].push(tab); } // 將obj轉成array const data = []; Object.keys(hash).forEach(key => data.push({ tabs: hash[key], windowId: Number(key), isCurWindow: Number(key) === currentTab.windowId })); // 進行排序,將當前窗口的順序往上提,保證更好的體驗 data.sort((winA, winB) => { if(winA.isCurWindow) { return -1; } else if(winB.isCurWindow) { return 1; } else { return 0; } }); return data; }3.3 TabList.js
根據App.js中的設計,我們可以先搭起代碼的骨架:
export class TabsList extends React.PureComponent { renderLoading() { return (); } renderOK() { // TODO... } renderException() { return ( ); } render() { const {status} = this.props; switch(status) { case STATUS.LOADING: return this.renderLoading(); case STATUS.OK: return this.renderOK(); case STATUS.EXCEPTION: default: return this.renderException(); } } }
接下來就是renderOK的實現,由于沒有固定的設計稿,我們可以盡情發揮自己的想象。這里借助antd粗略地實現了一版交互(加入了切換tab、搜索和刪除等操作),具體代碼考慮到篇幅就不貼了,感興趣的可以進這里查看。
4. 完結整個插件的制作過程,到這兒就已經完了。如果你有更好的idea或設計,可以提PR哦~通過這次學習,熟悉了對Tabs的操作,同時對Chrome插件的制作流程也算是有了更進一步的感悟。
5. 參考Chrome extensions dev guide
Chrome extensions browserAction
Chrome extensions tabs
本文所有代碼托管在這兒,覺得還行的可以star一下,也可以關注我的Blog。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104955.html
摘要:代碼代碼戳這里插件預備知識首先給出一本參考的中文書籍,在練習的過程中有幫到忙。你還可以重寫別的頁面,比如書簽管理頁面等,可以參考文檔中文翻譯過來應該叫內容腳本,它可以運行在你指定的頁面之中,可以拿到指定頁面的一些信息。 前言 這是一篇關于Chrome擴展插件入門、Vue.js入門的小練習,功能是:在當前瀏覽的頁面點擊擴展圖標,并點擊保存之后,該頁面就會存在你的新標簽頁中。其實就是一個可...
摘要:預加載自定義事件第三方擴展插件涉及的,除了,其它所有手機瀏覽器及瀏覽器均無法使用,目前主要包括語音輸入事件相關注意瀏覽器沒有事件事件相關的,手機端瀏覽器均可使用端模擬手機瀏覽器也可以正常使用。 最近項目中需要使用MUI做一個視頻播放的小功能。我就花時間研究了一下MUI。 MUI是一個使用JavaScript開發Android和IOS應用的前端框架。這篇文章將以知識樹的形式對MUI的使用...
摘要:在這篇文章中,我將列出我最喜歡的快捷鍵,這些快捷鍵讓我更快的編寫代碼,也讓編碼變得更有趣,以下是個快捷鍵,分享給你。打開鍵盤快捷鍵或,搜索。在中,啟動性能是很重要的。逐個選擇文本可以通過快捷鍵右箭頭右箭頭和左箭頭左箭頭逐個選擇文本。 為了保證的可讀性,本文采用意譯而非直譯。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 注意:自己嘗試的時候,Mac(17, p...
摘要:以下示例將阻止所有對的請求。從存儲請求和阻止請求的對象中刪除當前選項卡的屬性。收聽消息告知后臺進程阻止的列表已被用戶更新。兩者都提供類似的功能和事件處理程序。 前言 當我們瀏覽網站時,都會發送許多請求來獲取網頁內容。這些請求中有些是重要的,而有些是我們不需要,因為它們可能是廣告或建議等。在本文中,將創建一個有助于阻止和取消阻止所選URL的Chrome擴展插件,讓你選擇你打開的網址及該打...
摘要:特指度度量的是選擇器識別元素的精確性。為中的各個變量賦予相應的數值,就能得到特指度。為類選擇器屬性選擇器和偽類的數量。該文件包含選項卡組的樣式。易于混淆的屬性,應用注釋予以說明。屬性按照字母順序排列。屬性值為時,省略單位。 1、什么是優秀的架構 (1)優秀的架構是可預測的(2)優秀的架構是可擴展的(3)優秀的架構可提升代碼復用性(4)優秀的架構可擴展(5)優秀的架構可維護什么時候可以重...
閱讀 2805·2023-04-26 01:00
閱讀 745·2021-10-11 10:59
閱讀 2973·2019-08-30 11:18
閱讀 2666·2019-08-29 11:18
閱讀 1017·2019-08-28 18:28
閱讀 3009·2019-08-26 18:36
閱讀 2131·2019-08-23 18:16
閱讀 1065·2019-08-23 15:56