引言
大家好啊,我是前端拿破轮。
作为一个前端工程师,Chrome在我们的工作中扮演着重要作用。它不仅是前端的主要运行环境,而且是我们代码调试的重要工具,也是平时学习生活使用的重要软件。
在Chrome中有许多优秀的扩展,包括但不限于我们使用的React Devtools
,沉浸式翻译插件
等等。许多扩展极大地拓展了浏览器的能力边界,提高了我们工作和学校的效率。
所以笔者早有开发一个自己的插件的想法,苦于之前一直忙于学习和工作,没有合适的时间,最近忙里偷闲,决定抽出时间来进行这项工作。而且AI的时代浪潮也给浏览器的插件带来了新的生命力。所以笔者决定做一款浏览器端的AI助手插件。
本文属于Chrome插件开发专栏,会持续更新拿破轮的Chrome插件开发过程,欢迎各位读者订阅,希望对你有所帮助。如果还没有看过之前文章的读者,可以先阅读专栏中前面的文章。
阅读时间估计Demo
概述
在本文中我们会构建一个扩展程序,用来统计当前页面的阅读时间。
在本文中我们会涉及到以下概念:
- 扩展程序清单
- 扩展程序使用的图标大小
- 如何使用内容脚本将代码注入网页
- 如何使用匹配模式
- 扩展程序的权限
步骤
第 1 步:添加扩展程序的相关信息
与在从零到一开发一个Chrome插件(一)中的步骤相同,还是要创建一个目录用来构建我们的扩展程序,并在根目录中创建一个清单,即manifest.json
文件。
我们这里为了方便,直接在上一篇文章中的Demo的基础上进行改造。
json
// manifest.json
{
"name": "Reading time",
"description": "估计网页阅读时间",
"version": "1.0",
"manifest_version": 3,
}
关于扩展程序清单(manifest.json
)文件,我们有以下几点需要注意:
- 它必须位于根目录当中。
- 在这个文件中必须包含的键有
manifest_version
,name
,version
。 - 它在开发期间支持注释(
//
),但是必须将注释移除后,才能将插件上传到Chrome应用商店。
第 2 步:提供图标
在开发过程中图标可有可无,但是如果我们想要在Chrome应用商店中上架我们的扩展,就必须提供图标。
在这个Demo示例中,我们可以使用官方提供的GitHub图标仓库,将images
文件夹也放在我们的插件的根目录下,然后在清单文件manifest.json
中进行配置。

json
{
"name": "Reading time",
"description": "估计网页阅读时间",
"version": "1.0",
"manifest_version": 3,
"icons": {
"16": "images/icon-16.png",
"32": "images/icon-32.png",
"48": "images/icon-48.png",
"128": "images/icon-128.png"
}
}
官方推荐使用png
文件,当然其他文件也可以,但是svg
不行。
在上面的配置中我们可以看到,有16
,32
,48
,128
这四种尺寸的图标,他们分别在哪里显示呢?如下表所示:
图标大小 | 图标使用 |
---|---|
16x16 | 扩展程序页面和上下文菜单中的图标。 |
32x32 | Windows 计算机通常需要此大小。 |
48x48 | 显示在"扩展程序"页面上。 |
128x128 | 会在安装过程中和 Chrome 应用商店中显示。 |
第 3 步:声明内容脚本
扩展程序可以运行脚本,来读取和修改网页内容。这些脚本称为内容脚本 。他们运行在隔离环境 中。这意味着他们可以更改自己的JavaScript
环境,而不会与其托管页面或其他扩展程序的内容脚本发生冲突。
我们可以将下面的代码添加到manifest.json
文件中来注册一个名为content.js
的内容脚本。
json
{
"name": "Reading time",
"description": "估计网页阅读时间",
"version": "1.0",
"manifest_version": 3,
"icons": {
"16": "images/icon-16.png",
"32": "images/icon-32.png",
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
"content_scripts": [
{
"js": ["scripts/content.js"],
"matches": [
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/webstore/*"
]
}
]
}
matches
字段可以有一个或多个匹配模式 。这些标记可以让浏览器确定要将内容脚本注入到哪些网站 。匹配模式一般由以下三个部分组成:<scheme>://<host><path>
。可以包含*
字符。
第 4 步:计算并插入阅读时间
内容脚本可以使用标准DOM读取和更改网页内容。
该扩展会首先检查网页是否包含<article>
元素。然后,它会统计元素中所有字词,并创建一个段落来显示总阅读时间。
在根目录下创建scripts
文件夹,在其中创建一个content.js
文件,然后添加如下代码。
javascript
function renderReadingTime(article) {
// 如果没有article,就不需要计算,直接返回
if (!article) {
return;
}
// 得到文本数据
const text = article.textContent;
// 正则表达式匹配单词: 匹配一个或多个非空白字符
const wordMatchRegExp = /[^\s]+/g;
// 匹配的单词
const words = text.matchAll(wordMatchRegExp);
// 单词数量
const wordCount = [...words].length;
// 阅读时间:每分钟大概读200词
const readingTime = Math.round(wordCount / 200);
// 展示标签DOM
const badge = document.createElement("p");
// 增加css类名
badge.classList.add("color-secondary-text", "type--caption");
// 添加展示内容
badge.textContent = `⏱️ 预估阅读时间为 ${readingTime} 分钟`;
// 获取标题
const heading = article.querySelector("h1");
// 获取时间
const date = article.querySelector("time")?.parentNode;
// 在date的后面加入时间卡片,如果没有date,就在heading后面加入
(date ?? heading).insertAdjacentElement("afterend", badge);
}
renderReadingTime(document.querySelector("article"));
第 5 步:监听更改
使用我们上面的代码,如果我们使用左侧导航栏切换文章,阅读时间不会添加到新文章中 。这是因为现在很多网站都是作为单页应用(SPA)实现的,这些应用使用History API执行软导航。
所以当我们点击导航栏切换文章时,看起来浏览器的地址栏中的URL变化了,但是实际上我们请求的一直是根文件index.html
,所以renderReadingTime
函数不会重新执行,阅读时间自然而然就不会添加到新文章中。
为了解决这个问题,我们可以使用MutationObserver
来监听更改,并将阅读时间添加到新文章。
所以,我们需要再content.js
中添加如下代码:
javascript
function renderReadingTime(article) {
// 如果没有article,就不需要计算,直接返回
if (!article) {
return;
}
// 得到文本数据
const text = article.textContent;
// 正则表达式匹配单词: 匹配一个或多个非空白字符
const wordMatchRegExp = /[^\s]+/g;
// 匹配的单词
const words = text.matchAll(wordMatchRegExp);
// 单词数量
const wordCount = [...words].length;
// 阅读时间:每分钟大概读200词
let readingTime = Math.round(wordCount / 200);
// 确保阅读时间最少为 1
readingTime = Math.max(readingTime, 1);
// 展示标签DOM
const badge = document.createElement("p");
// 增加css类名
badge.classList.add("color-secondary-text", "type--caption");
// 添加展示内容
badge.textContent = `⏱️ 预估阅读时间为 ${readingTime} 分钟`;
// 获取标题
const heading = article.querySelector("h1");
// 获取时间
const date = article.querySelector("time")?.parentNode;
// 在date的后面加入时间卡片,如果没有date,就在heading后面加入
(date ?? heading).insertAdjacentElement("afterend", badge);
}
renderReadingTime(document.querySelector("article"));
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node instanceof Element && node.tagName === 'ARTICLE') {
renderReadingTime(node);
}
}
}
})
observer.observe(document.querySelector('devsite-content'), {
childList: true,
});
验证项目的文件结构是否如下所示:

按照从零到一开发一个Chrome插件(一)的步骤加载本地未打包的扩展程序来进行测试。
由于我们在content.js
中写的代码只针对Chrome Developer
中的文章,所以我们要用这些文章来进行测试。下面是两个可以进行测试的示例文档。

然后我们可以看到已经在标题下方成功显示了这篇文章的阅读时间。
总结
本文我们通过配置内容脚本,实现了在Chrome Dev的博客页面插入预估阅读时间的插件Demo,对于Chrome插件开发有了基本的认识和理解。
本文属于Chrome插件开发专栏,会持续更新拿破轮的Chrome插件开发过程,欢迎各位读者订阅,希望对你有所帮助。
好了,这篇文章就到这里啦,如果对您有所帮助,欢迎点赞,收藏,分享👍👍👍。您的认可是我更新的最大动力。由于笔者水平有限,难免有疏漏不足之处,欢迎各位大佬评论区指正。
往期推荐✨✨✨
我是前端拿破轮,关注我,一起学习前端知识,我们下期见!