今天看有人发文章专门介绍Chrome插件,我必需要说,插件开发就是一个摆弄一个小玩具,第一要素是实用,其次是好玩。 单纯罗列各类功能是很是无趣的。 因此把一篇旧文拿出来与你们分享。css
人,活着就是为了赖皮。html
做为一个合格的开发人员,把30%的时间用来赖皮(上班偷懒)是值得推荐的。node
由于,若是你工做时间没法赖皮,并不能说明你工做认真,只能说明你的工做自动化程度不够。git
赖皮狗,通常会在上班时间浏览:SGamer论坛、虎扑论坛、斗鱼、BiliBili这一类的网站。github
但在浏览过程当中会遇到如下痛点:web
因此,咱们须要:chrome
90%的上班族都在使用Chrome浏览器赖皮,因此咱们选择采用Chrome插件来实现功能。json
Chrome插件没什么大不了的,依然仍是采用HTML\CSS\JS的组合。windows
在这里,我将手把手带你从零开始制做插件。api
就像node.js的package.json同样,每个插件必须有一个manifest.json,做为最初配置文件。
咱们建立一个新的项目,在根目录下建立manifest.json,填入如下代码
==mainfest.json==
{
"name": "上班一键赖皮工具",
"version": "0.1",
"description": "windows:按Alt+S开启、关闭赖皮网站\nmac:按Control+S开启、关闭赖皮网站",
"manifest_version": 2
}
复制代码
解释一下:
接下来请右键保存虎扑logo到根目录,名字仍是如apple-touch-icon.png
就行吧。
也能够点击 user-gold-cdn.xitu.io/2018/12/15/… 保存图片
修改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
复制代码
点击Chorme右上角的三个点按钮...
> More Tools
> Extensions
在右上角把Developer mode打开
再找到顶部的LOAD UNPACKED
,把项目的根目录导入进去
项目导入后会出现一个新的卡片,是这个效果:
这时,你若是在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接口,就建立了一个新的标签页。 刷新一下插件,再试一试快捷键功能————是否是已经能控制浏览器弹出标签 页了!
稍显复杂的地方是标签页isOpen状态的处理, 下图主要关注isOpen状态的变化,以及tabCache的值变化。
graph TD
A(开始步骤:判断isOpen状态)-->|true| Y(情形1:清空tabCache缓存)
Y-->B(关闭全部符合域名的标签页)
A-->|false| C(情形2:检查标签页状态)
B --> X(将关闭的标签存入tabCache缓存数组)
X --> D(将isOpen状态改成false)
C --> |tabCache缓存有数据|E(把tabCache缓存的全部标签打开)
C --> |tabCache缓存没数据|F(查看是否有域名内标签)
F --> |没有域名内标签|G(新标签页打开主页)
F --> |有域名内标签|H(查看当前页是否在域名内)
G --> I(将isOpen状态改成true)
H --> |当前标签页不是域名内标签|K(定位到最近打开的域名内页面)
H --> |当前标签页就在域名内|J(关闭全部符合标签标签页)
E --> L(将isOpen状态改成true)
K --> M(将isOpen状态改成true)
J --> N(将isOpen状态设置为false)
复制代码
具体后台逻辑以下,能够跟据备注、对照流程图进行理解:
//初始化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
咱们把mainPageUrl
和myPattern
改为从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页面,若是点击图标就会显示,如图:
下面咱们完善一下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">×</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">×</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>')
}
复制代码
好了,一个优雅的赖皮插件就作好了,你们能够查看 github.com/wanthering/…
你你你,别老躲厕所玩手机了,臭!一样赖着把工资挣了,我们赖皮插件用起来!
祝你们上班赖得开心。