Chrome插件開發入門:如何實現一鍵上班賴皮

不少人介紹過Chrome插件,但必需要說,插件開發就是擺弄一個小玩具,第一要素是實用,其次是好玩。 單純羅列各類功能是很是無趣的。 因此把一篇舊文拿出來與你們分享。

人,活着就是爲了賴皮。css

做爲一個合格的開發人員,把30%的時間用來賴皮(上班偷懶)是值得推薦的。html

由於,若是你工做時間沒法賴皮,並不能說明你工做認真,只能說明你的工做自動化程度不夠。node

賴皮狗,通常會在上班時間瀏覽:SGamer論壇、虎撲論壇、鬥魚、BiliBili這一類的網站。git

但在瀏覽過程當中會遇到如下痛點:github

  • 老闆查崗,貼子或直播間打開太多,不能及時關閉所有的賴皮站點。
  • 老闆走了從新賴皮,不記得以前打開的貼子或直播間在哪裏。
  • 每次在瀏覽器裏輸入賴皮網址,打字真的很麻煩!
  • 工做時打開了太多標籤頁,休息時很難找到想要的賴皮頁面。

因此,咱們須要:web

簡單的一鍵賴皮插件須要怎樣的體驗:

  1. 打開瀏覽器後,一個快捷鍵,當即打開賴皮頁面,喜滋滋開始賴皮的一天。
  2. 老闆/leader查崗時,一個快捷鍵,當即關閉全部賴皮頁面。
  3. 老闆走後,或工做一段時間後,一個快捷鍵,當即打開原來的賴皮貼子和直播間。

簡單的一鍵賴皮插件功能:

  1. 包含簡單的一鍵賴皮站點功能
  2. 能自定義配置賴皮網站。
  3. 上傳Google,發佈插件。

從零開始,開發簡單的一鍵賴皮插件

90%的上班族都在使用Chrome瀏覽器賴皮,因此咱們選擇採用Chrome插件來實現功能。chrome

Chrome插件沒什麼大不了的,依然仍是採用HTMLCSSJS的組合。json

在這裏,我將手把手帶你從零開始製做插件。windows

mainfest.json

就像node.js的package.json同樣,每個插件必須有一個manifest.json,做爲最初配置文件。api

咱們建立一個新的項目,在根目錄下建立manifest.json,填入如下代碼

==mainfest.json==

{
  "name": "上班一鍵賴皮工具",
  "version": "0.1",
  "description": "windows:按Alt+S開啓、關閉賴皮網站\nmac:按Control+S開啓、關閉賴皮網站",
  "manifest_version": 2
 }

解釋一下:

  • name: 插件的名字
  • version: 插件的版本
  • description: 插件簡介欄
  • manifest_version: 這個是寫死的,每一個文件必須有

接下來請右鍵保存虎撲logo到根目錄,名字仍是如apple-touch-icon.png就行吧。

image

也能夠點擊
https://b1.hoopchina.com.cn/c... 保存圖片

修改mainfest.json,設置四個尺寸的icon都變成apple-touch-icon.png,以及插件欄也顯示apple-touch-icon.png。

==mainfest.json==

{
  "name": "上班一鍵賴皮工具",
  "version": "0.1",
  "description": "windows:按Alt+S開啓、關閉賴皮網站  \nmac:按Control+S開啓、關閉賴皮網站",
  "icons": {
    "16": "apple-touch-icon.png",
    "32": "apple-touch-icon.png",
    "48": "apple-touch-icon.png",
    "128": "apple-touch-icon.png"
  },
  "browser_action": {
    "default_icon": "apple-touch-icon.png",
    "default_popup": "popup.html"
  },
  "commands": {
    "toggle-tags": {
      "suggested_key": {
        "default": "Alt+S",
        "mac": "MacCtrl+S"
      },
      "description": "Toggle Tags"
    }
  },
  "manifest_version": 2
}

解釋一下:

  • icons: 配置了顯示在不一樣地方的圖標
  • browser_action: 即右上角插件,browser_action > default_icon即右上角插件圖標
  • commands:通常用於快捷鍵命令。 commands > toggle-tags > suggested_key之下,設置了快捷鍵,只要按下快捷鍵,即會向Chrome就會向後臺發佈一個command,值爲toggle-tags
在windows環境下,咱們將快捷鍵設置成 Alt+S,在mac環境下,咱們將快捷鍵設置成 Control+S,配置文件中寫做 MacCtrl+S

如今咱們有了命令,就須要後臺腳本接收,在mainfest.json中添加後臺腳本:
==mainfest.json==

...
  "background": {
    "scripts": [
      "background.js"
    ]
  }
...

並在根目錄下建立background.js.
==background.js==

chrome.commands.onCommand.addListener(function(command) {
    alert(command)
    console.log(command)
})

如今咱們的目錄結構以下:

├── manifest.json
└── background.js
└── sgamers.png

在chrome內加載插件

點擊Chorme右上角的三個點按鈕...> More Tools > Extensions

image

在右上角把Developer mode打開

image

再找到頂部的LOAD UNPACKED,把項目的根目錄導入進去

image

項目導入後會出現一個新的卡片,是這個效果:

image

這時,你若是在Windows中按下Alt+S就會彈出消息,消息爲toggle-tags,正好是咱們在mainfest.json中定義好的。

同時咱們能夠點擊上圖藍色鍵頭所指示的background page,打開一個調試工具,能夠看到toggle-tags的輸出。

咱們在以後本地編輯插件後,能夠按灰色鍵頭所指的刷新,新功能就能當即刷新加載了!

有了這些工做,意味着你能夠進入下一步了!

標籤頁配置

一鍵打開/關閉賴皮網站,實現原理其實就是chrome的標籤頁功能。

標籤頁功能訪問須要在manifest.json中添加權限
==mainfest.json==

...
  "permissions": ["tabs"]
  ...

接下來,咱們寫一下background.js實現經過快捷鍵(windows的Alt+S,或mac的Ctrl+S)建立新的主頁:
==background.js==

// 輸入你想要的網站主頁
const MainPageUrl = 'http://https://bbs.hupu.com/all-gambia'

chrome.commands.onCommand.addListener(function (command) {
  if (command === 'toggle-tags') {
    chrome.tabs.create({"url": MainPageUrl, "selected": true});
  }
})

其實實現很簡單,就是調用chrome.tabs.create接口,就建立了一個新的標籤頁。
刷新一下插件,再試一試快捷鍵功能————是否是已經能控制瀏覽器彈出標籤 頁了!
image

實現具體邏輯:

稍顯複雜的地方是標籤頁isOpen狀態的處理,
下圖主要關注isOpen狀態的變化,以及tabCache的值變化。

clipboard.png

具體後臺邏輯以下,能夠跟據備註、對照流程圖進行理解:

//初始化isOpen和tabCache狀態
let isOpen = false
let tabCache = []

//新標籤打開的主頁
const mainPageUrl = 'https://bbs.hupu.com/all-gambia'
//四個賴皮網站的正則匹配表達式
const myPattern = 'sgamer\.com/|douyu\.com|hupu\.com|bilibili\.com'
//當前頁面的Url
let currentPageUrl = ''

/**
 * 開始步驟: 判斷isOpen狀態
 * 情形一:isOpen爲true,則移除頁面
 * 情形二:isOpen爲false,則重載頁面
 */
chrome.commands.onCommand.addListener(function (command) {
  if (command === 'toggle-tags') {
    if (isOpen) {
      //情形一:isOpen爲true
      removePages(myPattern)
      //情形二:isOpen爲false
    } else {
      reloadPages(myPattern, mainPageUrl)
    }
  }
})


/**
 * 情形1:移除頁面
 * 一、清空tabCache緩存
 * 二、關閉全部域名內標籤
 * 三、將關閉的標籤存入tabCache緩存數組
 * 四、將isOpen狀態改成false
 */
function removePages(patternStr) {
  tabCache = []
  chrome.tabs.query({active: true}, function (tab) {
    currentPageUrl = tab[0].url
  })
  let pattern = new RegExp(patternStr)
  walkEveryTab(function (tab) {
    if (pattern.test(tab.url)) {
      chrome.tabs.remove(tab.id,function(){
        tabCache.push(tab.url)
      })
    }
  },function(){
    isOpen = false
  })
}

/**
 * 情形2:重載頁面
 * 判斷有沒有緩存:
 *    情形2-1無緩存:開啓新標籤或定位到域名內的標籤
 *    情形2-2有緩存:打開所有緩存內的頁面
 */
function reloadPages(patternStr, mainPageUrl) {
  if (tabCache.length === 0) {
    focusOrCreateTab(patternStr, mainPageUrl)
  } else {
    openAllCachedTab(tabCache)
  }
}

/**
 * 情形2-1:開啓新標籤或定位到域名內的標籤
 * 一、遍歷所有標籤,記錄符合域名的標籤的url,以及最後一個標籤頁
 * 二、若是沒有符合域名的標籤,則建立主頁,並將isOpen狀態改成true
 * 三、若是有符合域名的標籤:
 *        一、獲取當前的頁面url
 *        二、若是當前頁面url不符合域名,則定位到這個標籤頁,將isOpen狀態改成true
 *        三、若是當前頁面url符合域名,則關閉全部標籤頁(按情形1處理),將isOpen狀態改成false
 */
function focusOrCreateTab(patternStr, url) {
  let pattern = new RegExp(patternStr)
  let theTabs = []
  let theLastTab = null
  walkEveryTab(function (tab) {
      if (pattern.test(tab.url)) {
        theTabs.push(tab.url)
        theLastTab = tab
      }
    }, function () {
      if (theTabs.length > 0) {
        chrome.tabs.query({active: true}, function (tab) {
          let currentUrl = tab[0].url
          if (theTabs.indexOf(currentUrl) > -1) {
            removePages(patternStr)
            isOpen = false
          } else {
            chrome.tabs.update(theLastTab.id, {"selected": true});
            isOpen = true
          }
        })
      } else {
        chrome.tabs.create({"url": url, "selected": true});
        isOpen = true
      }
    }
  )
}

/**
 * 情形2-2:
 * 一、把tabCache全部標籤頁從新打開
 * 二、將isOpen狀態改成true
 */
function openAllCachedTab(tabCache) {
  let focusTab = null
  tabCache.forEach(function (url, index) {
    chrome.tabs.create({'url': url}, function (tab) {
      if (tab.url === currentPageUrl) {
        focusTab = tab.id
      }
      if (index === tabCache.length-1 - 1) {
        if (focusTab) {
          chrome.tabs.update(focusTab, {"selected": true},function(){
          });
        }
      }
    })
  })
  isOpen = true
}




/**
 *
 * @param callback
 * @param lastCallback
 * 包裝一下遍歷所有標籤的函數,建立兩個回調。
 * 一個回調是每一次遍歷的過程當中就執行一遍。
 * 一個回調是所有遍歷完後執行一遍。
 */
function walkEveryTab(callback, lastCallback) {
  chrome.windows.getAll({"populate": true}, function (windows) {
    for (let i in windows) {
      let tabs = windows[i].tabs;
      for (let j in tabs) {
        let tab = tabs[j];
        callback(tab)
      }
    }
    if(lastCallback) lastCallback()
  })
}

上傳與發佈插件

咱們須要在Chrome的開發者中心發佈插件,進入 Developer Dashboard

好了,一個簡單易用的上班賴皮插件作好了!在調試模式下,你能夠用ctrl+s來快捷尋找、打開、關閉、從新打開賴皮頁面。隨時隨地、全方位賴皮,從容面對老闆查崗。

可配置的高級賴皮插件

如今我但願個人插件均可以隨時配置站點:

那麼就須要用到chrome.storage了。

你須要打開storage權限:

manifest.json內添加

...
  "permissions": [
    "tabs","storage"
  ],
...

而後使用

chrome.storage.local.set({
        'value1':theValue1,
        'value2',theValue2
})

這種形式來存放storage。
這後使用

chrome.storage.local.get(['value1'],(res)=>{
    const theValue1 = res.value1
})

這樣獲得存放的value值。

開始改寫background.js

咱們把mainPageUrlmyPattern改爲從storage中獲取。

const INIT_SITES_LIST = ['bilibili.com','douyu.com','sgamer.com','hupu.com']
const INIT_MAIN_PAGE = 'https://bbs.hupu.com/all-gambia'

// 在安裝時即設置好storage
chrome.runtime.onInstalled.addListener(function() {
  chrome.storage.local.set({
    sites: INIT_SITES_LIST,
    mainPage:INIT_MAIN_PAGE
  })
});


//初始化isOpen和tabCache狀態
let isOpen = false
let tabCache = []
let currentPageUrl = ''

/**
 * 開始步驟: 判斷isOpen狀態
 * 情形一:isOpen爲true,則移除頁面
 * 情形二:isOpen爲false,則重載頁面
 */
chrome.commands.onCommand.addListener(function (command) {
  if (command === 'toggle-tags') {
    chrome.storage.local.get(['sites','mainPage'],function(res){
      let sites =  res.sites
      let mainPageUrl = res.mainPage
      let myPattern = sites.map(item=>item.replace('.','\\.')).join('|')
      console.log(myPattern)

      if (isOpen) {
        //情形一:isOpen爲true
        removePages(myPattern)
        //情形二:isOpen爲false
      } else {
        reloadPages(myPattern, mainPageUrl)
      }
    })

  }
})
// ======================== 下面的部分不須要改動,看到這裏就夠了)

/**
 * 情形1:移除頁面
 * 一、清空tabCache緩存
 * 二、關閉全部域名內標籤
 * 三、將關閉的標籤存入tabCache緩存數組
 * 四、將isOpen狀態改成false
 */
function removePages(patternStr) {
  tabCache = []
  chrome.tabs.query({active: true}, function (tab) {
    currentPageUrl = tab[0].url
  })
  let pattern = new RegExp(patternStr)
  walkEveryTab(function (tab) {
    if (pattern.test(tab.url)) {
      chrome.tabs.remove(tab.id,function(){
        tabCache.push(tab.url)
      })
    }
  },function(){
    isOpen = false
  })
}

/**
 * 情形2:重載頁面
 * 判斷有沒有緩存:
 *    情形2-1無緩存:開啓新標籤或定位到域名內的標籤
 *    情形2-2有緩存:打開所有緩存內的頁面
 */
function reloadPages(patternStr, mainPageUrl) {
  if (tabCache.length === 0) {
    focusOrCreateTab(patternStr, mainPageUrl)
  } else {
    openAllCachedTab(tabCache)
  }
}

/**
 * 情形2-1:開啓新標籤或定位到域名內的標籤
 * 一、遍歷所有標籤,記錄符合域名的標籤的url,以及最後一個標籤頁
 * 二、若是沒有符合域名的標籤,則建立主頁,並將isOpen狀態改成true
 * 三、若是有符合域名的標籤:
 *        一、獲取當前的頁面url
 *        二、若是當前頁面url不符合域名,則定位到這個標籤頁,將isOpen狀態改成true
 *        三、若是當前頁面url符合域名,則關閉全部標籤頁(按情形1處理),將isOpen狀態改成false
 */
function focusOrCreateTab(patternStr, url) {
  let pattern = new RegExp(patternStr)
  let theTabs = []
  let theLastTab = null
  walkEveryTab(function (tab) {
      if (pattern.test(tab.url)) {
        theTabs.push(tab.url)
        theLastTab = tab
      }
    }, function () {
      if (theTabs.length > 0) {
        chrome.tabs.query({active: true}, function (tab) {
          let currentUrl = tab[0].url
          if (theTabs.indexOf(currentUrl) > -1) {
            removePages(patternStr)
            isOpen = false
          } else {
            chrome.tabs.update(theLastTab.id, {"selected": true});
            isOpen = true
          }
        })
      } else {
        chrome.tabs.create({"url": url, "selected": true});
        isOpen = true
      }
    }
  )
}

/**
 * 情形2-2:
 * 一、把tabCache全部標籤頁從新打開
 * 二、將isOpen狀態改成true
 */
function openAllCachedTab(tabCache) {
  let focusTab = null
  tabCache.forEach(function (url, index) {
    chrome.tabs.create({'url': url}, function (tab) {
      if (tab.url === currentPageUrl) {
        focusTab = tab.id
      }
      if (index === tabCache.length-1 - 1) {
        if (focusTab) {
          chrome.tabs.update(focusTab, {"selected": true},function(){
          });
        }
      }
    })
  })
  isOpen = true
}




/**
 *
 * @param callback
 * @param lastCallback
 * 包裝一下遍歷所有標籤的函數,建立兩個回調。
 * 一個回調是每一次遍歷的過程當中就執行一遍。
 * 一個回調是所有遍歷完後執行一遍。
 */
function walkEveryTab(callback, lastCallback) {
  chrome.windows.getAll({"populate": true}, function (windows) {
    for (let i in windows) {
      let tabs = windows[i].tabs;
      for (let j in tabs) {
        let tab = tabs[j];
        callback(tab)
      }
    }
    if(lastCallback) lastCallback()
  })
}

那麼咱們能夠寫一個popup頁面,若是點擊圖標就會顯示,如圖:

image

下面咱們完善一下popup.html和popup.css和pupup.js頁面

全部js文件均可以直接調用chrome.storage.local.get

只須要特別注意一下js文件的chrome.storage調用部分

其它的拷貝便可,咱們不是來學頁面佈局的

popup.html

<html>
<head>
  <title>經常使用網站配置頁面</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
  <h2 class="lapi-title">經常使用賴皮站點域名</h2>
  <ul class="lapi-content">
  </ul>
  <p>
  <label><input type="text" id="add" class="add"></label>
  <button class="button add-button ">+</button>
  </p>
  <p></p>
  <p></p>
  <p></p>
  <h2>個人賴皮主頁</h2>
  <div id="change-content">
  <span class="main-page-inactive" id="main-page-inactive"></span><button class="button change-button " id="change">✎</button>
  </div>

  <p class="lapi-tip">按<span class="lapi-key">Alt+S</span>快速開啓/關閉賴皮站點</p>
</div>
<script src="zepto.min.js"></script>
<script src="popup.js"></script>
</body>
</html>

popup.css

* {
    margin: 0;
    padding: 0;
    color:#6a6f77;
}


input, button, select, textarea {
    outline: none;
    -webkit-appearance: none;
    border-radius: 0;
    border: none;
}

input:focus{
    list-style: none;
    box-shadow: none;
}

ol, ul {
    list-style: none;
}

li{
    margin: 5px 0;
}

.container {
    width: 200px;
    padding: 10px;
}

.container h2{
    margin: 10px;
    text-align: center;
    display: block;
}

.lapi-content li{
    transition: opacity 1s;
}

.site{
    cursor: pointer;
    color: #00b0ff;
}

.add, .main-page{
    box-sizing: border-box;
    text-align:center;
    font-size:14px;
    /*height:27px;*/
    border-radius:3px;
    border:1px solid #c8cccf;
    color:#6a6f77;
    outline:0;
    padding:0 10px;
    text-decoration:none;
    width: 170px;
}

#main-page{
    font-size: 12px;
    text-align: left;
    width: 166px;
    margin: 0;
    padding: 2px;
}

.add{
    height: 27px;
}

.main-page{
    width: 170px;
    outline: none;
    resize: none;

}

.main-page-inactive{
    width: 160px;
    line-break: auto;
    word-break: break-word;
    overflow: hidden;
    display: inline-block;
    cursor: pointer;
    color: #00b0ff;
    margin: 3px;
}

.button{

    font-size: 16px;
    /*border: 1px solid #c8cccf;*/
    color: #c8cccf;
    /*border: none;*/
    padding: 0 4px 1px 3px;
    border-radius: 3px;
}

.close-button{
    transition: all 1s;
}

.button:hover{
    background: #E27575;
    color: #FFF;
}

.add-button{
    transition:all 1s;
    font-size: 20px;
    padding: 0 6px 1px 5px;
}

.change-button{
    position: absolute;
    transition:all 1s;
    font-size: 20px;
    padding: 0 6px 1px 5px;
}
.change-button:hover{
    background: #f9a825;
    color: #FFF;
}

#change-check{
    color: #f9a825;
}

#change-check:hover{
    color: #fff;
}

.add-button:hover{
    background: #B8DDFF;
    color: #FFF;
}

.submit{
    transition: all 1s;
    margin: 10px;
    padding: 5px 10px;
    font-size: 16px;
    border-radius: 4px;
    background: #B8DDFF;
    border: 1px solid #B8DDFF;
    color: #FFF;
}

.submit:hover{
    border: 1px solid #B8DDFF;
    background: #fff;
    color: #B8DDFF;
}

.fade{
    opacity: 0;
}

.add-wrong,.add-wrong:focus{
    border: #e91e63 1px solid;
    box-shadow:0 0 5px rgba(233,30,99,.3);
}

.lapi-tip{
    margin-top: 20px;
    border-top: 1px solid #c8cccf;
    padding-top: 8px;
    color: #c8cccf;
    text-align: center;
}

.lapi-key{
    color: #B8DDFF;

}

重點關注:chrome.storage部分

popup.js

let sites = []
let mainPage = ''
const isMac = /Macintosh/.test(navigator.userAgent)
let $lapiKey = $('.lapi-key')

isMac? $lapiKey.text('Control+S'):$lapiKey.text('Alt+S')

// 從storage中取出site和mainPage字段,並設置在頁面上。
chrome.storage.local.get(['sites','mainPage'], function (res) {
  if (res.sites) {
    sites = res.sites
    mainPage = res.mainPage
    sites.forEach(function (item) {
      let appendEl = '<li><span class="site">' + item + '</span>\n' +
        '<button class="button close-button">&times</button>\n' +
        '</li>'
      $('ul.lapi-content').append(appendEl)
    })

  }
  $('#main-page').val(mainPage)
  $('#main-page-inactive').html(mainPage)
})


$('#save').on('click', function () {
  alert()
})

$('#change-content').delegate('#main-page-inactive','click',function(){
  let mainPageUrl = $(this).html()
  if(/^http:\/\/|^https:\/\//.test(mainPageUrl)){
    chrome.tabs.create({"url": mainPageUrl, "selected": true})
  }else{
    chrome.tabs.create({"url": 'http://'+mainPageUrl, "selected": true})
  }
})


let addEl = $('#add')
addEl.focus()
let lapiCon = $('ul.lapi-content')

lapiCon.delegate('.close-button', 'click', function () {
  let $this = $(this)
  let siteValue = $this.siblings().html()
  sites = sites.filter(function (item) {
    return item !== siteValue
  })
  chrome.storage.local.set({sites: sites})
  $this.parent().addClass('fade')
  setTimeout(function () {
    $this.parent().remove()
  }, 800)
})


$('.add-button').on('click',addEvent)
addEl.bind('keypress',function(event){
  if(event.keyCode === 13) addEvent()
})


function addEvent(){
  if(!validate(addEl.val())){
    addEl.addClass('add-wrong')
  }else{
    let appendEl = '<li><span class="site">' + addEl.val() + '</span>\n' +
      '<button class="button close-button">&times</button>\n' +
      '</li>'
    $('ul.lapi-content').append(appendEl)
    sites.push(addEl.val())
    chrome.storage.local.set({sites:sites})
    addEl.removeClass('add-wrong')
    addEl.focus().val('')
  }
}

function validate(value){
  value = value.trim()
  if(value.length ===0){
    return false
  }
  return /^([\w_-]+\.)*[\w_-]+$/.test(value)
}

lapiCon.delegate('.site','click',function(){
  let siteUrl = $(this).html()
  chrome.tabs.create({"url": 'http://'+siteUrl, "selected": true})
})

$('#change-content').delegate('#change','click',function(){
  changeMainPage($(this))
}).delegate('#change-check','click',function(){
  changeCheck($('#change-check'))
}).delegate('#main-page','blur',function(){
  changeCheck($('#change-check'))
})


function changeMainPage($this){
  $this.siblings().remove()
  $this.parent().prepend('<label><textarea id="main-page" class="main-page"></textarea></label>')
  $this.parent().append('<button class="button change-button " id="change-check">✓</button>')
  $('#main-page').val(mainPage).focus()
  $this.remove()
}


function changeCheck($this){
  let mainPageVal = $('#main-page').val()
  $this.siblings().remove()
  $this.parent().prepend('<span class="main-page-inactive" id="main-page-inactive"></span>')
  $('#main-page-inactive').text(mainPageVal)
  chrome.storage.local.set({mainPage:mainPageVal})
  $this.parent().append('<button class="button change-button " id="change">✎</button>')
}

好了,一個優雅的賴皮插件就作好了,你們能夠查看
https://github.com/wanthering...

祝你們上班賴得開心。 別老躲廁所玩手機了,不健康!賴皮插件用起來!
相關文章
相關標籤/搜索