首發於微信公衆號《前端成長記》,寫於 2019.10.18javascript
有句老話說的好,好記性不如爛筆頭。人生中,總有那麼些東西你願去執筆寫下。css
本文旨在把整個開發的過程和遇到的問題及解決方案記錄下來,但願可以給你帶來些許幫助。html
安裝和源碼前端
在 《乾貨!從0開始,0成本搭建我的動態博客》 中,已經完成了動態博客的搭建。接下來,將圍繞該博客,開發對應的 Chrome拓展
,方便使用。java
本文不須要前期準備,直接跟我作就行了node
這裏主要分爲幾個大的功能點:git
Ⅰ.必要知識介紹github
Chrome 拓展插件
其實是由 HTML/CSS/JS/圖片
等資源組成的一個 .crx
的拓展包,解壓出來便可獲得真正內容。web
Chrome 拓展插件
對項目結構沒有要求,只須要在開發根目錄下有一個 mainfest.json
便可。chrome
進入 Chrome 拓展程序
頁面,打開 開發者模式
開始咱們的開發之路。
Ⅱ.基礎配置開發
首先,新建一個 src
目錄做爲插件的文件目錄,而後新建一個 mainfest.json
文件,文件內容以下:
// mainfest.json { // 插件名稱 "name": "McChen", // 插件版本號 "version": "0.0.1", // 插件描述 "description": "Chrome Extension for McChen.", // 插件主頁 "homepage_url": "https://chenjiahao.xyz", // 版本必須指定爲2 "manifest_version": 2 }
而後打開 Chrome
拓展程序頁面,點擊 加載已解壓的拓展程序 按鈕,選擇上面新建的 src
文件,將會看到以下兩處變化:
你會發現你的拓展插件已經添加到右上角了,點擊右鍵時出現的第一行爲 name
,點擊跳轉連接爲 homepage_url
。
接下來咱們爲咱們的拓展插件添加圖標,在 src
中新建一個名爲 icon.png
的圖標,而後修改 mainfest.json
文件:
// mainfest.json { ... "icons": { "16": "icon.png", "32": "icon.png", "48": "icon.png", "128": "icon.png" } ... }
點擊插件開發的更新圖標,咱們能夠看到圖標已經加上了:
這裏會發現,右上角的圖標爲何是置灰的呢?這裏就須要聊到 browser_action
和 page_action
。[參考文檔]
browser_action
:若是你想讓圖標一直可見,那麼配置該項page_action
:若是你不想讓圖標一直可見,那麼配置該項爲了讓圖標一直可見,咱們來修改下 mainfest.json
:
{ ... "browser_action": { "default_icon": "icon.png", "default_title": "McChen" }, ... }
此時再次更新查看效果:
到這裏,基礎的配置開發已經完成了,接下來就是功能部分。
Ⅲ.內容菜單導航開發
內容導航菜單我用在兩個地方:鼠標點擊右上角圖標的 Popup
和網頁中按鼠標右鍵出現的菜單。
先看看鼠標點擊右上角圖標 Popup
的,給 mainfest.json
增長 default_popup
就是 popup
展現的頁面內容了。
{ ... "browser_action": { "default_icon": "icon.png", "default_title": "McChen", "default_popup": "popup.html" }, ... }
新建一個 popup.html
文件,內容以下:
<!DOCTYPE html> <html lang="en"> <head> <title>McChen</title> <meta charset="utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> #McChen-container { padding: 4px 0; margin: 0; width: 80px; user-select: none; overflow: hidden; text-align: center; background-color: #f6f8fc;} .McChen-item_a { position: relative; display: block; font-size: 14px; color: #283039; transition: all 0.2s; line-height: 28px; text-decoration: none; white-space: nowrap; text-indent: 16px;} .McChen-item_a:before { position: absolute; top: 50%; margin-top: -14px; font-size: 16px; line-height: 28px;} .McChen-item_a:after { position: absolute; top: 50%; margin-top: -14px; font-size: 16px; line-height: 28px;} .McChen-item_a + .McChen-item_a { border-top: 1px solid #f0f2f5;} .McChen-item_a:hover { color: #0074ff;} .McChen-item_a:nth-child(1):before { content: '·'; left: 4px;} .McChen-item_a:nth-child(2):before { content: '··'; left: 2px;} .McChen-item_a:nth-child(3):before { content: '···'; left: 0;} .McChen-item_a:nth-child(4):before { content: '····'; left: -2px;} .McChen-item_a:nth-child(5):before { content: '····'; margin-top: -16px; left: -2px;} .McChen-item_a:nth-child(5):after { content: '·'; margin-top: -12px; left: -2px;} .McChen-item_a:nth-child(6):before { content: '····'; margin-top: -16px; left: -2px;} .McChen-item_a:nth-child(6):after { content: '··'; margin-top: -12px; left: -2px;} .McChen-item_a:nth-child(7):before { content: '····'; margin-top: -16px; left: -2px;} .McChen-item_a:nth-child(7):after { content: '···'; margin-top: -12px; left: -2px;} </style> </head> <body id="McChen-container"> <a class="McChen-item_a" href="https://chenjiahao.xyz" target="_blank">主頁</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/archives" target="_blank">博客</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/labels" target="_blank">標籤</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/links" target="_blank">友鏈</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/about" target="_blank">關於</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/board" target="_blank">留言</a> <a class="McChen-item_a" href="https://chenjiahao.xyz/blog/#/search" target="_blank">搜索</a> </body> </html>
咱們更新後來看看效果,點擊右上角圖標將會看到以下的內容彈窗:
下一步,咱們來實如今網頁中按鼠標右鍵出現的菜單。
首先,你必需要配置對應的權限才能使用這個 API
,還須要配置修改 mainfest.json
內容:
... "permissions": [ "contextMenus" ] ...
接下來,須要經過 API
調用去建立對應的菜單,這裏須要用到常駐在後臺運行的 js
才行,因此還須要修改 mainfest.json
文件:
... "background": { "scripts": [ "background.js" ] }, ...
而後咱們新建一個 backgroud.js
文件,文件內容以下:
chrome.contextMenus.create({ id: 'McChen', title: 'McChen', contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'home', title: '主頁', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'archives', title: '博客', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'labels', title: '標籤', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'links', title: '友鏈', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'about', title: '關於', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'board', title: '留言', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'search', title: '搜索', parentId: 'McChen', // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成爲父菜單項的子菜單 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); // 監聽菜單點擊事件 chrome.contextMenus.onClicked.addListener(function (info, tab) { if (info.menuItemId === 'home') { chrome.tabs.create({url: 'https://chenjiahao.xyz'}); } else { chrome.tabs.create({url: 'https://chenjiahao.xyz/blog/#/' + info.menuItemId}); } })
更新後,點擊鼠標右鍵將查看到以下內容:
至此,內容菜單導航功能已所有完成。
Ⅳ.地址欄搜索開發
地址欄搜索主要是經過 Omnibox
來實現的,咱們首先須要設置關鍵字,在這裏我設置成 'mc' ,修改 mainfest.json
文件:
... { "omnibox": { "keyword" : "mc" } } ...
更新後,咱們在地址欄輸入 mc
按 Tab
或者 Space
鍵可看到以下內容:
接下來咱們進行接口開發,因爲須要進行接口調用,因此須要配置容許請求的地址,修改 mainfest.json
文件:
... { "permissions": [ "contextMenus", // 容許請求所有https "https://*/" ], } ...
而後修改 background.js
文件內容:
... let timer = ''; chrome.omnibox.onInputChanged.addListener((text, suggest) => { if (timer) { clearTimeout(timer) timer = '' } else { timer = setTimeout(() => { if (text.length > 1) { const xhr = new XMLHttpRequest(); xhr.open("POST", "https://api.artfe.club/transfer/github", true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.onreadystatechange = function () { if (xhr.readyState === 4) { const list = JSON.parse(xhr.responseText).data.search.nodes; if (list.length) { suggest(list.map(_ => ({content: 'ISSUE_NUMBER:' + _.number, description: '文章 - ' + _.title}))) } else { suggest([ {content: 'none', description: '無相關結果'} ]) } } }; xhr.send('query=' + query); } else { suggest([ {content: 'none', description: '查詢中,請稍後...'} ]) } }, 300) } }); // 當選中建議內容時觸發 chrome.omnibox.onInputEntered.addListener((text) => { if (text.startsWith('ISSUE_NUMBER:')) { const number = text.substr(13) chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { if (tabs.length) { const tabId = tabs[0].id; const url = 'https://chenjiahao.xyz/blog/#/archives/' + number; chrome.tabs.update(tabId, {url: url}); } }); } }); ...
這裏有幾個地方須要注意一下:
onInputChanged
這方法觸發頻率高,和正常開發同樣,須要作一次函數防抖,要否則請求頻率會特別高。Promise
,因此我使用的 XMLHttpRequest
suggest
中 content
和 description
字段都不容許爲空,可是在事件回調裏須要識別,因此我這裏特地增長了一個前綴 ISSUE_NUMBER:
更新後,在地址欄輸入 mc
按 Tab
後,輸入 乾貨
,將會看到以下內容:
至此,地址欄搜索功能已所有完成。
Ⅴ.新文章推送開發
新文章推送功能,首先咱們須要知道以前的最新文章是哪篇,才能作到精準推送,因此這裏須要用到 Storage
,也就是存儲功能。存下最新文章的 ID
,輪詢最新文章,若是有更新,則存下最新文章的 ID
而且調用推送的 API
。因此,咱們須要先增長權限配置,修改 mainfest.json
文件:
... "permissions": [ "storage", "contextMenus", "notifications", "https://*/" ], ...
而後修改 'background.js' 文件內容:
... getLatestNumber(); chrome.storage.sync.get({LATEST_TIMER: 0}, function (items) { if (items.LATEST_TIMER) { clearInterval(items.LATEST_TIMER) } const LATEST_TIMER = setInterval(() => { getLatestNumber() }, 1000 * 60 * 60 *24) chrome.storage.sync.set({LATEST_TIMER: LATEST_TIMER}) }); function getLatestNumber () { const query = `query { repository(owner: "ChenJiaH", name: "blog") { issues(orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 1, after: null) { nodes { title number } } } }`; const xhr = new XMLHttpRequest(); xhr.open("POST", "https://api.artfe.club/transfer/github", true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { const list = JSON.parse(xhr.responseText).data.repository.issues.nodes; if (list.length) { const title = list[0].title; const ISSUE_NUMBER = list[0].number; chrome.storage.sync.get({ISSUE_NUMBER: 0}, function(items) { if (items.ISSUE_NUMBER !== ISSUE_NUMBER) { chrome.storage.sync.set({ISSUE_NUMBER: ISSUE_NUMBER}, function() { chrome.notifications.create('McChen', { type: 'basic', iconUrl: 'icon.png', title: '新文章發佈通知', message: title }); chrome.notifications.onClicked.addListener(function (notificationId) { if (notificationId === 'McChen') { chrome.tabs.create({url: 'https://chenjiahao.xyz/blog/#/archives/' + ISSUE_NUMBER}); } }) }); } }); } } }; xhr.send('query=' + query); } ...
注意:因爲是後臺常駐,因此須要增長輪詢來判斷是否有更新,我這裏設置的是一天一次
更新後,第一次咱們會看到瀏覽器右下角會有推送消息以下:
至此,新文章推送功能也已經開發完成了。
在拓展程序頁面點擊打包擴展程序,選擇 src
做爲根目錄打包便可。
將會生成 src.crx
和 src.pem
兩個文件, .crx
文件就是你提交到拓展商店的資源, .pem
文件是私鑰,下次進行打包更新時須要使用。
因爲打包須要 5$ ,因此我這裏就不作演示了,須要的能夠自行嘗試,[發佈地址]
一個基於動態博客的 Chrome 拓展插件
就開發完了,歡迎下載使用。
若有疑問或不對之處,歡迎留言。
(完)
本文爲原創文章,可能會更新知識點及修正錯誤,所以轉載請保留原出處,方便溯源,避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗
若是能給您帶去些許幫助,歡迎 ⭐️star 或 ✏️ fork
(轉載請註明出處:https://chenjiahao.xyz)