chrome 扩展是什么
chrome 扩展是用传统的 HTML、CSS、JS、图片等静态资源开发并最终打包成后缀为 .crx
的一个压缩包。所以,它和我们平常开发的页面没有多大的区别,所以如果你想引入前端开发所用的各种框架,组件库,构建工具也都是可以的。主要区别只有 2 个:
- 扩展的页面、js 和普通的页面运行位置不同
- 扩展可以调用 chrome 提供的更多的 API 来增强我们扩展的能力
chrome 扩展的安装方式
扩展的安装方式有 3 种:
- 通过 chrome 扩展商店,下载安装
- 在其他网站下载打包好的
.crx
压缩包,把压缩包直接拖拽到 chrome 的扩展管理页面 - 如果是自己开发的扩展,可以在扩展管理页面,打开开发者模式,手动加载已解压的扩展程序,进行本地调试
chrome 扩展的展现形式
这里只简单地介绍几种经常见到的,还有更多的展现形式,大家感兴趣可以去官方文档详细了解
- 点击地址栏右侧 icon 会有页面弹出,这个大多数扩展都会有,主要是扩展的设置或者功能的入口
- 页面修饰内容:通过添加 DOM 对页面赋予新功能,比如
Octotree
(对 gitub 项目页面做导航) - 页面右键菜单:定制在页面内右键弹出的菜单,很多划线翻译的扩展都利用了这个功能
- 覆盖 chrome 默认页面: chrome 有的页面支持开发者自定义,比如 Momentum 就覆盖了默认的 New Tab 页面
- devtool 工具:这个是开发者经常用到的 比如
vue-devtool
等框架提供的调试工具
开发介绍
具体扩展各个组成部分的学习,我们以一个很简单的例子为基础进行介绍,这个扩展是一个为页面添加回到顶部功能的扩展。
配置文件
每一个扩展都必须要有的一个名字为 manifest.json 的配置文件,这个文件声明了此扩展用到了哪些功能,及各个功能需要用到的静态资源
这个 backToTop 扩展的配置已经在途中说明,已经有了基本的说明,下面对一些配置项做下额外的说明,全部配置可以在官网查看:
- browser_action 指定了 popup 页面相关的 icon、html、tooltip 文字等配置,相似的还有一个 page_action,它的配置参数和 browser_aciton 是相同的 但是它可以通过
chrome.pageAction
API 来动态的设置扩展在某些页面的行为 - icons 配置不用每个尺寸都给出,chrome 会自己选出效果最合适的 icon
- permissions 声明扩展需要用到的 chrome 特性
常用 API
- chrome.runtime
- chrome.tabs 对标签页进行操作、与对应标签页内容通讯
- chrome.storage 扩展的存储,类似 localstorage
- chrome.contextMenus
- chrome.extension
核心 JS
这部分我们说下扩展开发核心的几种 JS
popup
popup 页面生命周期是点击弹出时,初始化,关闭时,页面也跟着销毁, 并且这个页面没有任何跨域的限制。它在我们扩展里的作用是配置页面里 backToTop icon 的样式,并存入storage。
1 | chrome.storage.local.get(['right', 'bottom', 'icon'], function(result) { |
2 | if(result) { |
3 | // 初始化 |
4 | right.value = result.right || ''; |
5 | bottom.value = result.bottom || ''; |
6 | if(result.icon) { |
7 | img.src = result.icon; |
8 | img.dataset.uploaded = true; |
9 | } |
10 | } |
11 | }) |
页面初始化时,从 storage 取出存储的值,初始化页面。
保存参数时,再将相关参数存入 storage
1 | chrome.storage.local.set({right: rightVal, bottom: bottomVal}); |
2 | if(img.dataset.uploaded) { |
3 | chrome.storage.local.set({icon: img.src}); |
4 | } |
注: storage API 有 2 种 storage.sync
和 storage.local
他们的区别
- sync 会将存储的数据定时发到 chrome 的服务器,进行数据的同步,local 就只是将数据存储在本地
- 尺寸的不同:local 和
localstorage
是一样的 5M. 而 sync 存储的大小只有 100K 而且对于单个 key 的值大小,以及写入的频率也有限制,毕竟要同步服务端,所以如果开发的扩展只是用于个人使用的效率提升,不打算发布,可以直接用 local 就好了
content-script
content-script
是我们用来定制化页面,实现页面内扩展逻辑的地方。它的特点是:
- 因为在页面内,当然可以访问 DOM 但是和页面的 js 是完全独立的,不能互相访问
- 无法对页面内的 DOM 事件绑定 扩展里的回调,这种情况可以通过
content-script
创建一个 script 标签插入到 DOM 里 这个新的 script 里的函数是可以绑定的 - 因为在页面里运行,所以是会收到跨域限制滴
- 运行时机是随着页面的加载而运行,页面关闭也就卸载了,所以说
content-script
会在每一个 tab 页面里都有一份代码在运行 - 因为在页面初始化才会运行,所以在初始加载插件时,需要刷新页面,
content-script
才会开始运行。 - 注入页面的 css 优先级非常高,一定要注意好类名 ID 名的设置
那我们的 backToTop 里 content-script
都干了什么呢
首先,初始化我们页面里的 icon 并根据页面 scrollTop
判断当前是否需要展示 icon
1 | const el = document.createElement('div'); |
2 | el.show = true; // 控制icon是否显示 |
3 | el.classList.add('ce-btt-container'); |
4 | |
5 | |
6 | el.style.opacity = target.scrollTop > visibleHeight ? 1 : 0; |
7 | |
8 | const img = document.createElement('img'); |
9 | img.classList.add('ce-btt-icon'); |
10 | |
11 | el.appendChild(img); |
12 | |
13 | |
14 | chrome.storage.local.get(['right', 'bottom', 'icon'], function(result) { |
15 | el.style.right = result && result.right ? result.right + 'px' : right; |
16 | el.style.bottom = result && result.bottom ? result.bottom + 'px' : bottom; |
17 | img.src = result && result.icon ? result.icon : chrome.runtime.getURL('icons/backToTop.png'); |
18 | document.body.appendChild(el); |
19 | }); |
第二步, 要想实现返回顶部,当然要给我们的 icon 绑定点击事件,以及监听 scroll
事件判断什么时候该隐藏展示
1 | el.addEventListener('click', function(e) { |
2 | let step = 20; |
3 | let timer = setInterval(() => { |
4 | if(target.scrollTop <= 0) { |
5 | clearInterval(timer); |
6 | } else { |
7 | step += 20; |
8 | target.scrollTop -= step; |
9 | } |
10 | }, 20); |
11 | }); |
12 | |
13 | const handleScroll = function() { |
14 | if(!el.show) return false; // icon不显示时,不处理 |
15 | if(target.scrollTop > visibleHeight) { |
16 | el.style.opacity = 1; |
17 | } else { |
18 | el.style.opacity = 0; |
19 | } |
20 | }; |
21 | |
22 | container.addEventListener('scroll', throttle(handleScroll, 300)); |
第三步, 如果 popup 页面有配置的变更, content-script
都需要立刻进行更新
1 | chrome.storage.onChanged.addListener(function(changes, namespace) { |
2 | if(changes.bottom) { |
3 | el.style.bottom = `${changes.bottom.newValue}px`; |
4 | } |
5 | if (changes.right) { |
6 | el.style.right = `${changes.right.newValue}px`; |
7 | } |
8 | if (changes.icon) { |
9 | img.src = changes.icon.newValue; |
10 | } |
11 | }); |
注: 这里需要注意的是,因为我们要把扩展里的 icon 插入到页面,所以需要在 manifest.json
里配置 web_accessible_resources
赋予页面可以访问我们指定的扩展静态资源的权限。这是因为页面里 icon 的 src 属性是这样的
background
background
可以理解为是扩展在后台一直运行的一个 JS(实际并不是), 它在整个浏览器里只会有一个 js 在运行。在 background 的配置里,有一个 persistent
的配置, 当它为 true 时,background 才会一直运行,false 时,浏览器会检测长时间不活动时,自动卸载调,只有监听的事件发生时,才会重新执行,官方的说明是
The only occasion to keep a background script persistently active is if the extension uses chrome.webRequest API to block or modify network requests. The webRequest API is incompatible with non-persistent background pages.
所以绝大多数时候,我们把它设为 false 就可以了。另外 background
也是可以跨域的,
所以我们可以总结出,除了页面内的 js chrome 对其他的 js 都没有跨域的限制。
好,我们看看我们扩展里 background
干了啥
首先,初始时,肯定要监听浏览器的初始化事件,才可以绑定扩展关注的事件。
1 | chrome.runtime.onInstalled.addEventListener(function() { |
2 | // init extention |
3 | }) |
第二步,因为有的页面已经提供了返回顶部的功能,所以这个时候我们需要提供可以把我们的 icon 永久隐藏的功能,我们在 background
初始化的逻辑中,创建一个鼠标右键的菜单项,这个菜单项可以实现 切换我们的 icon 显示状态的功能
1 | chrome.contextMenus.create({ |
2 | title: 'toggle', |
3 | id: 'toggle' |
4 | }); |
5 | |
6 | chrome.contextMenus.onClicked.addListener(function() { |
7 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { |
8 | sendMsg(tabs.length ? tabs[0].id : null); |
9 | }); |
10 | }); |
11 | |
12 | function sendMsg(tabId) { |
13 | chrome.tabs.sendMessage(tabId, 'toggle', function(res) { |
14 | console.log(res); |
15 | }); |
16 | } |
发送消息这里我们用了 chrome.tabs
API 因为我们每个 tab 都会有一个 content-script
所以需要筛选出当前所在的标签页,然后发送消息。
最后,我们的 content-script
需要监听发送消息的事件,并切换 icon 的状态
1 | chrome.runtime.onMessage.addListener(function(req, sender, respond) { |
2 | if(req === 'toggle') { |
3 | if (target.scrollTop > visibleHeight) { |
4 | el.style.opacity = el.style.opacity === '1' ? 0 : 1; |
5 | } |
6 | el.show = !el.show; |
7 | respond('toggle success'); |
8 | } |
9 | }); |
至此,整个扩展的功能就基本介绍完了,过程中用到的 API 这里并不作详细的介绍,详细使用还是需要大家去 官网 查看
调试
最后说一下如何调试,调试个人认为还是比较麻烦的, 代码变更并没有热更新,所以需要我们手动去扩展管理页重新加载。而且几种不同的 JS 调试的位置也不同,设计通讯时经常需要在几个不同的地方来回切换
popup
调试:在弹出的窗口里,右键审查元素就可以弹出调试窗口,调试方式和普通的页面调试没有区别。
background
调试: 在管理页点击背景页,就可以弹出调试窗口了
content-script
调试: 因为是在页面运行,所以调试的地方和页面 js 是在一个窗口里
代码位置:在 sources tab 下选中 Content scripts 就可以看到页面加载的全部的扩展 contnt-script
了。
console 输出: 在 Console tab 下拉框里选中要调试的扩展,就可以看到对应扩展的 console 输出。
打包发布
在扩展管理页,可以打包扩展程序,打包后就可以生成 .crx
文件
打包之后,就可以发布了,不过发布需要先花 5$ 注册开发者,我就没有继续下去了。
总结
在开发过程中,可以发现写一个扩展其实并不难,用到的技术都是前端每天都在用的东西。只要我们多加留心,就会发现使用 chrome 过程中有许多可以提效,优化体验的地方,这时候我们就可以试着用扩展的方式解决。总体来说,chrome 扩展是一种技术成本很低,就可以干些有趣的事情的技术
参考资料: