1、前言
筆者以前一直想了解一些關於谷歌插件的相關知識,但願經過谷歌插件能夠更好的認識到谷歌的調試工具,同時也想着可使用谷歌插件去寫一些小工具,既學習了新的東西,又有必定的趣味性。恰逢近段時間須要分享,所以花了兩週時間學習和了解谷歌插件相關知識,本篇文章就將筆者在學習過程當中的一些思考分享給你們。固然,由於時間的緣由,若是筆者對於這一塊的認識有不對的地方,歡迎批評指正~css
2、什麼是谷歌插件
下面先介紹一下谷歌插件的主要組成部分,由於目前谷歌插件使用比較廣泛的版本爲 2.0 版本,所以本文都是基於 2.0 版本進行使用說明,3.0 版本相較於 2.0 版本更爲簡便,感興趣的同窗能夠點擊文章末尾處的連接瞭解更多相關知識。html
(一)配置文件react
谷歌插件的核心文件就是配置文件--manifest.json(清單)文件。
其中,manifest.json 文件最基本的 Api 以下:git
{ "name": "chrome extension", "version": "1.0.0", "manifest_version": 2, "description": "A litlle chrome extension demo" }
主要是包含所寫谷歌插件的名稱、版本以及相關描述,其中 manifest_version 表示清單文件版本。
manifest.json 做爲谷歌插件的核心部分,筆者認爲該文件對插件來講就至關於一個入口配置文件,開發人員只須要在這個文件經過配置相應的 js,調用谷歌瀏覽器提供的 Api,就能實現達到完善這個插件的目的。github
一、基本使用Apichrome
在清單文件中有 許多的Api,筆者就不一一列舉了,下面爲你們介紹幾個筆者認爲比較重要的 Api,經過如下幾個 Api 的介紹,但願可使讀者對於谷歌插件的開發過程有一個大概的認識。json
1)browser_action跨域
{ ... "browser_action": { "default_icon": { "16": "images/get_started16.png", "32": "images/get_started32.png" }, "default_title": "谷歌劃詞翻譯", "default_popup": "popup.html" }, ... }
browser_action可設置瀏覽器右上角的圖標及名稱。
default_popup 可配置點擊圖標後會出現的一個小窗口,這裏能夠作一些臨時性的操做瀏覽器
2)permissionsantd
{ ... "permissions": [ "activeTab", "storage", "tabs", "contextMenus" ], ... }
permissions 可配置谷歌插件權限申請,如 contextMenus(右鍵菜單)、 tabs(標籤)和storage(插件本地存儲)。
3)content_scripts
{ ... "content_scripts": { "matches": ["<all_urls>"], "css": ["content/content_script.css"], "js": ["content/content_script.js"] }, ... }
content-scripts其實就是谷歌插件中向頁面注入腳本的一種形式(雖然名爲 script,其實還能夠包括 CSS 的),藉助 content-scripts 能夠實現經過配置的方式輕鬆向指定頁面注入 JS 和 CSS。
4)background
{ ··· "background": { "scripts": ["background.js"], "persistent": false }, ··· }
background 是一個常駐的頁面,它的生命週期是插件中全部類型頁面中最長的,它隨着瀏覽器的打開而打開,隨着瀏覽器的關閉而關閉,因此一般把須要一直運行的、啓動就運行的及全局的代碼放在 background 裏面。
筆者也畫了一個上面涉及到的腳本在瀏覽器中的分佈,以下圖:
以上就是筆者認爲比較重要的一些Api,在介紹完以後,感興趣的同窗就能夠開始着手寫幾個簡單的工具,用來實現本身遠大的抱負以及理想,昇華本身高貴的靈魂。
一些同窗可能不太知道開發谷歌插件的前置條件,在這裏爲你們簡單介紹下。
首先須要打開管理擴展程序,打開開發者模式。
點擊加載已解壓的程序按鈕便可加載本地谷歌插件,開發的時候代碼若是有更新的話,須要刷新已加載插件,點擊關閉後再開啓,沒必要刷新開發頁面。
在瞭解完前置條件後,筆者將在下文中爲你們分享谷歌劃詞翻譯插件從0-1的實現過程,經過開發這個工具也能夠加深對於你們谷歌插件的認識。
3、谷歌劃詞翻譯插件
谷歌翻譯算是筆者使用比較頻繁的插件,對於在網頁上看到的不懂的英文單詞或者句子,直接使用鼠標選中,輕鬆快捷的翻譯出相應的中文。所以在學習的過程當中,筆者就在想谷歌瀏覽器插件的翻譯工具是如何實現的呢?
(一)思考
如何去作一個劃詞翻譯插件,首先要考慮的有如下幾點:
- 如何實現翻譯效果
- 如何選中咱們須要的元素
- 選中元素以後如何展現劃詞翻譯面板
- 全部的瀏覽器 Tab 都須要支持翻譯效果
思考完上面的這些點後,帶着這幾個疑惑,筆者將在下文一一解答,同時也列舉一下遇到的一些點。
(二)劃詞翻譯面板
首先不去考慮該插件的功能,先寫下劃詞翻譯的面板的樣式,所達到的效果以下:
HTML 代碼以下:
<div class="translate-panel show"> <header>谷歌劃詞翻譯插件<span class="close">X</span></header> <main> <div class="source"> <div class="title">英文</div> <div class="content">test</div> </div> <div class="result"> <div class="title">簡體中文</div> <div class="content">...</div> </div> </main> </div>
將上面的樣式簡單寫好以後,開始考慮如何將劃詞翻譯的面板展現在瀏覽器當前頁面。對於谷歌瀏覽器來講,在網頁上進行的交互是屬於 content_scripts 的,須要引入劃詞翻譯面板所須要的 JS 或者 CSS來生成當前面板。
其次,在配置文件中配置 content_scripts引入 JS 文件,動態的生成 DOM 元素。大體的思路就是經過監聽到鼠標鬆開後,去生成翻譯面板,在生成的元素上面添加 opacity 樣式控制顯隱,使用谷歌免費翻譯 Api 進行翻譯。
其中代碼以下所示:
// manifest.json { ... "content_scripts": { "matches": ["<all_urls>"], "css": ["content_script.css"], "js": ["content_script.js"] }, "permissions": [ "activeTab" ], ... } // content_script.js class TranslatePanel { createPanel = () => { let wrapper = document.createElement('div') wrapper.innerHTML = ` <header>谷歌劃詞翻譯插件<span class="close">X</span></header> <main> <div class="source"> <div class="title">英文</div> <div class="content">test</div> </div> <div class="result"> <div class="title">簡體中文</div> <div class="content">...</div> </div> </main> ` wrapper.classList.add('translate-panel') wrapper.querySelector('.close').onclick = () => { this.wrapper.classList.remove('show') } document.body.appendChild(wrapper) this.wrapper = wrapper } showPanel = () => { this.wrapper.classList.add('show') } translateSelect = (content) => { const source = this.wrapper.querySelector('.source .content') const result = this.wrapper.querySelector('.result .content') source.innerHTML = content result.innerHTML = '翻譯中...' fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${content}`) .then(res => res.json()) .then(res => { result.innerHTML = res[0][0][0] }) } locationPanel = (target) => { this.wrapper.style.top = target.y + 'px' this.wrapper.style.left = target.x + 'px' } } let panel = new TranslatePanel() panel.createPanel() window.onmouseup = (target) => { // 獲取選中內容 const content = window.getSelection().toString().trim() if (!content) return panel.locationPanel({ x: target.pageX, y: target.pageY }) panel.translateSelect(content) panel.showPanel() }
在上面過程的中,筆者使用了谷歌免費的翻譯接口,可是這個接口按照目前的設置仍是有一點問題,咱們暫時不表。如今劃詞翻譯的面板就已經基本寫好了。
(三)腳本通訊
劃詞翻譯插件開發到這裏,細心的同窗應該發現了,每次選中單詞時都會觸發劃詞翻譯功能,此時急需一個控制翻譯功能的開關,這個開關就能夠放在 popup 腳本上面。
具體的樣式的實現就不去介紹了,主要看一下 HTML 結構:
<div class="switch-wrapper"> <div class="switch-desc">是否啓用劃詞翻譯</div> <input type="checkbox" class="switch" /> </div>
基本效果以下:
此時面板和劃詞翻譯的面板都已經有了,再考慮一下如何實現 popup 腳本與 content_script 腳本之間的通訊。首先,在
popup 腳本,咱們在打開窗口的時候須要去查詢是否有存儲開啓劃詞翻譯的狀態,同時,
同時當狀態發生變動的時候須要將其存儲時,再在當前的Tab下面發送請求。
// popup.js let switchWrapp = document.querySelector('.switch') chrome.storage.sync.get(['checked'], (target) => { if (target) { switchWrapp.checked = target.checked } }) switchWrapp.onclick = (e) => { chrome.storage.sync.set({ checked: e.target.checked }) chrome.tabs.query( {active: true, currentWindow: true }, (tabs) => { chrome.tabs.sendMessage(tabs[0].id, { checked: e.target.checked }) }) }
上面代碼中的 chrome.storage 可用於存儲數據,追蹤數據。storage.sync 的做用是讓谷歌瀏覽器的數據同步,這使得在不一樣 Tab 頁上面切換的狀態也是能夠同步的,同時也不用將數據保存在 background 後臺頁面中,storage還有不少Api好比監聽 storage數據變化的onChanged,這裏就不一一介紹了。
將開啓或關閉劃詞翻譯的狀態發送後,content_script.JS 須要添加監聽事件,獲取到該狀態後,進行關閉或開啓操做。
// content_script.js let checked = false window.onmouseup = (target) => { ··· if (!content || !checked) return ··· } chrome.storage.sync.get(['checked'], (target) => { if (target) checked = target.checked }) chrome.runtime.onMessage.addListener((target) => { if (target) { checked = target.checked } })
在開發過程當中,發如今當前的 Tab 是能夠去完成這個操做的,可是當開啓了多個 Tab 的狀況下就會出現開啓翻譯卻不能展現翻譯面板的狀況。
針對以上狀況筆者思考了一下,此時應該將checked儲存起來,不該該放在 content_script 腳本當中。
// content_script.js let panel = new TranslatePanel() panel.createPanel() window.onmouseup = (target) => { // 獲取選中內容 const content = window.getSelection().toString().trim() if (!content) return window.chrome.storage.sync.get(['checked'], (result) => { if (result.checked) { panel.locationPanel({ x: target.pageX, y: target.pageY }) panel.translateSelect(content) panel.showPanel() } }) } chrome.runtime.onMessage.addListener((target) => { if (target.type == 'CHECKED') { chrome.storage.sync.set({ checked: target.checked }) } })
以上,popup 腳本和 content_script 腳本之間就實現了通訊,翻譯插件也能夠經過 popup 上面的按鈕,進行開啓或關閉翻譯功能。
同理,也能夠知道其餘模塊也是能夠經過這種方式去進行通訊,不一樣的是其餘腳本向 content_script 通訊是須要使用 tabs,先查找到當前的 Tab 在發送請求。
(四)右鍵直達翻譯頁面
當關閉劃詞翻譯的時候,直接沒法翻譯選中內容也不是很友好,這個時候能夠設置爲點擊右鍵的時候出現翻譯菜單項。由於這部份內容須要一直存在就加在 background 中。
// backgrond.js // 當擴展程序第一次安裝、更新至新版本或 Chrome 瀏覽器更新至新版本時產生 chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ "id": "SELECT_TRANSLATE", "title": "翻譯 %s", "contexts": ["selection"] }) }) chrome.contextMenus.onClicked.addListener((target) => { if (target.menuItemId == 'SELECT_TRANSLATE') { chrome.tabs.create({url: `https://translate.google.cn/?sl=en&tl=zh-CN&text=${target.selectionText}&op=translate`}) } })
(五)跨域問題
開發過程當中,有的同窗也看出來了一個問題,好比說谷歌的這個翻譯的 Api 須要同源的狀況下才能正常調用該接口,而後就只能在谷歌翻譯的頁面中使用劃詞翻譯,場面一度十分尷尬...
那麼,正常來講這個劃詞翻譯使用起來也是十分不合理的,接下來就須要解決一下這個跨域的問題。
筆者當時想要嘗試的是使用 JSONP,也就是去使用嵌入腳本去進行跨域,發現仍是會有一些問題,主要是谷歌的翻譯的接口不支持回調函數。
同時也去查閱了一些資料,發現是能夠在 content_script 中通知 background,background 後臺去調用谷歌翻譯的 Api 是來避免這個狀況的。
主要由於 background 的權限很是高,幾乎能夠調用全部的 Chrome 擴展 Api,並且它能夠無限制跨域,也就是能夠跨域訪問任何網站而無須要求對方設置 CORS。
具體添加的代碼以下:
// content_script.js translateSelect = (content) => { const source = this.wrapper.querySelector('.source .content') const result = this.wrapper.querySelector('.result .content') source.innerHTML = content result.innerHTML = '翻譯中...' chrome.runtime.sendMessage({ type: 'QUERY_TRANSLATE', queryContent: content }, (res) => { result.innerHTML = res[0][0][0] }) } // background.js chrome.runtime.onMessage.addListener((request, sender, callBack) => { if (request.type == 'QUERY_TRANSLATE') { fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${request.queryContent}`) .then(res => res.json()) .then(res => { callBack(res) }) return true } })
background 中的發送消息的監聽事件返回 true 是爲了與 content_script 的消息通道保持打開,經過異步的方式發送請求。
如今想一想,若是使用插件的 background 就能夠去跨域去進行請求一些藉口,使用不得當的話感受仍是很危險的,能夠去獲取其餘網站的一些信息,因而可知,仍是要慎重的進行此操做。
(六)待完善的點
- 支持其餘語言的翻譯,谷歌翻譯的接口有兩個 Api,sl(文本翻譯以前的語言) 和 tl(文本須要翻譯成的語言) 可經過改變對應的值支持其餘語言的翻譯;
- 樣式完善,實現先選中圖標在進行翻譯。多添加一步感受對交互更友好,不用去進行開關的操做。
代碼結構
介紹完了劃詞翻譯插件,筆者本來是打算再分享一個關於谷歌 devtool 開發工具,開發一個相似於 React Developer Tools 的本地開發工具,可是因爲時間也是不太夠,涉及到的點比較多,同時查閱了不少的資料對於這一塊的介紹也是比較的淺,因此仍是決定着重於介紹劃詞翻譯。
相信經過劃詞翻譯的開發能使得讀者比較快速的認識到谷歌插件,除了谷歌插件外,若是對於 devtool 工具插件開發有興趣的同窗也能夠去了解一下。
另外,有的同窗可能會認爲目前開發的效率是有一點低的,如今的話谷歌插件的開發也是能夠基於 react + antd 去進行開發的,也是能夠達到高效快速的去開發一個插件的效果。
4、結尾
在學習谷歌插件的時候,筆者遇到了一些問題,好比說文檔比較少,官方文檔又是英文的還常常 404,不過呢,好在谷歌瀏覽器有提供了不少 Api,筆者這邊在寫插件的時候也感受到了不少趣味性。本篇文章仍是寫的比較通俗易懂,若是有什麼點寫的不對或着不清晰的地方,歡迎你們留言進行探討。
相關資源及參考:
- 官方文檔:https://developer.chrome.com/docs/extensions/mv3/
- react + antd 腳手架:https://github.com/jhen0409/react-chrome-extension-boilerplate
- 谷歌翻譯插件完整代碼:https://github.com/ting0130/chrome-extensions-translate
本文整理自:技術乾貨丨谷歌插件開發探索及其應用
數棧是雲原生—站式數據中臺PaaS,咱們在github和gitee上有一個有趣的開源項目:FlinkX,記得給咱們點個star!star!star!
gitee開源項目:https://gitee.com/dtstack_dev_0/flinkx
github開源項目:https://github.com/DTStack/flinkx
FlinkX是一個基於Flink的批流統一的數據同步工具,既能夠採集靜態的數據,好比MySQL,HDFS等,也能夠採集實時變化的數據,好比MySQL binlog,Kafka等,是全域、異構、批流一體的數據同步引擎,你們若是有興趣,歡迎來github社區找咱們玩~