这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

环境搭建

Vue Web-Extension - A Web-Extension preset for VueJS (vue-web-extension.netlify.app)

npm install -g @vue/cli
npm install -g @vue/cli-init
vue create --preset kocal/vue-web-extension my-extension
cd my-extension
npm run server

会提供几个选项,如Eslint,background.js,tab页,axios,如下图

选择完后,将会自动下载依赖,通过npm run server将会在根目录生成dist文件夹,将该文件拖至Chrome插件管理便可安装,由于使用了webpack,所以更改代码将会热更新,不用反复的编译导入。

项目结构

├─src
| ├─App.vue
| ├─background.js
| ├─main.js
| ├─manifest.json
| ├─views
| | ├─About.vue
| | └Home.vue
| ├─store
| | └index.js
| ├─standalone
| | ├─App.vue
| | └main.js
| ├─router
| | └index.js
| ├─popup
| | ├─App.vue
| | └main.js
| ├─override
| | ├─App.vue
| | └main.js
| ├─options
| | ├─App.vue
| | └main.js
| ├─devtools
| | ├─App.vue
| | └main.js
| ├─content-scripts
| | └content-script.js
| ├─components
| | └HelloWorld.vue
| ├─assets
| | └logo.png
├─public
├─.browserslistrc
├─.eslintrc.js
├─.gitignore
├─babel.config.js
├─package.json
├─vue.config.js
├─yarn.lock

根据所选的页面,并在src与vue.config.js中配置页面信息编译后dist目录结构如下

├─devtools.html
├─favicon.ico
├─index.html
├─manifest.json
├─options.html
├─override.html
├─popup.html
├─_locales
├─js
├─icons
├─css

安装组件库

安装elementUI

整体的开发和vue2开发基本上没太大的区别,不过既然是用vue来开发的话,那肯定少不了组件库了。

要导入Element-ui也十分简单,Vue.use(ElementUI); Vue2中怎么导入element,便怎么导入。演示如下

不过我没有使用babel-plugin-component来按需引入,按需引入一个按钮打包后大约1.6m,而全量引入则是5.5左右。至于为什么不用,因为我需要在content-scripts.js中引入element组件,如果使用babel-plugin-component将无法按需导入组件以及样式(应该是只支持vue文件按需引入,总之就是折腾了我一个晚上的时间)

安装tailwindcss

不过官方提供了如何使用TailwindCSS,这里就演示一下

在 Vue 3 和 Vite 安装 Tailwind CSS - Tailwind CSS 中文文档

推荐安装低版本,最新版有兼容性问题

npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

创建postcss.config.js文件

// postcss.config.js
module.exports = {
plugins: [
// ...
require('tailwindcss'),
require('autoprefixer'), // if you have installed `autoprefixer`
// ...
]
}

创建tailwind.config.js文件

// tailwind.config.js
module.exports = {
purge: {
// Specify the paths to all of the template files in your project
content: ['src/**/*.vue'], // Whitelist selectors by using regular expression
whitelistPatterns: [
/-(leave|enter|appear)(|-(to|from|active))$/, // transitions
/data-v-.*/, // scoped css
],
}
// ...
}

在src/popup/App.vue中导入样式,或在新建style.css在mian.js中import "../style.css";

<style>
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */ @tailwind utilities;
</style>

从官方例子导入一个登陆表单,效果如下

项目搭建

页面搭建

页面搭建就没什么好说的了,因为使用的是element-ui,所以页面很快就搭建完毕了,效果如图

悬浮窗

悬浮窗其实可有可无,不过之前写Chrome插件的时候就写了悬浮窗,所以vue版的也顺带写一份。

要注意的是悬浮窗是内嵌到网页的(且在document加载前载入,也就是"run_at": "document_start"),所以需要通过content-scripts.js才能操作页面Dom元素,首先在配置清单manifest.json与vue.confing.js中匹配要添加的网站,以及注入的js代码,如下

  "content_scripts": [
{
"matches": ["https://www.bilibili.com/video/*"],
"js": ["js/jquery.js", "js/content-script.js"],
"css": ["css/index.css"],
"run_at": "document_start"
},
{
"matches": ["https://www.bilibili.com/video/*"],
"js": ["js/jquery.js", "js/bilibili.js"],
"run_at": "document_end"
}
]
	contentScripts: {
entries: {
'content-script': ['src/content-scripts/content-script.js'],
bilibili: ['src/content-scripts/bilibili.js'],
},
},

由于是用Vue,但又要在js中生成组件,就使用document.createElement来进行创建元素,Vue组件如下(可拖拽)

:::danger

如果使用babel-plugin-component按需引入,组件的样式将无法载入,同时自定义组件如果编写了style标签,那么也同样无法载入,报错:Cannot read properties of undefined (reading 'appendChild')

大致就是css-loader无法加载对应的css代码,如果执意要写css的话,直接在manifest.json中注入css即可

:::

完整代码

// 注意,这里引入的vue是运行时的模块,因为content是插入到目标页面,对组件的渲染需要运行时的vue, 而不是编译环境的vue (我也不知道我在说啥,反正大概意思就是这样)
import Vue from 'vue/dist/vue.esm.js';
import ElementUI, { Message } from 'element-ui';
Vue.use(ElementUI); // 注意,必须设置了run_at=document_start此段代码才会生效
document.addEventListener('DOMContentLoaded', function() {
console.log('vue-chrome扩展已载入'); insertFloat();
}); // 在target页面中新建一个带有id的dom元素,将vue对象挂载到这个dom上。
function insertFloat() {
let element = document.createElement('div');
let attr = document.createAttribute('id');
attr.value = 'appPlugin';
element.setAttributeNode(attr);
document.getElementsByTagName('body')[0].appendChild(element); let link = document.createElement('link');
let linkAttr = document.createAttribute('rel');
linkAttr.value = 'stylesheet';
let linkHref = document.createAttribute('href');
linkHref.value = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
link.setAttributeNode(linkAttr);
link.setAttributeNode(linkHref);
document.getElementsByTagName('head')[0].appendChild(link); let left = 0;
let top = 0;
let mx = 0;
let my = 0;
let onDrag = false; var drag = {
inserted: function(el) {
(el.onmousedown = function(e) {
left = el.offsetLeft;
top = el.offsetTop;
mx = e.clientX;
my = e.clientY;
if (my - top > 40) return; onDrag = true;
}),
(window.onmousemove = function(e) {
if (onDrag) {
let nx = e.clientX - mx + left;
let ny = e.clientY - my + top;
let width = el.clientWidth;
let height = el.clientHeight;
let bodyWidth = window.document.body.clientWidth;
let bodyHeight = window.document.body.clientHeight; if (nx < 0) nx = 0;
if (ny < 0) ny = 0; if (ny > bodyHeight - height && bodyHeight - height > 0) {
ny = bodyHeight - height;
} if (nx > bodyWidth - width) {
nx = bodyWidth - width;
} el.style.left = nx + 'px';
el.style.top = ny + 'px';
}
}),
(el.onmouseup = function(e) {
if (onDrag) {
onDrag = false;
}
});
},
}; window.kz_vm = new Vue({
el: '#appPlugin',
directives: {
drag: drag,
},
template: `
<div class="float-page" ref="float" v-drag>
<el-card class="box-card" :body-style="{ padding: '15px' }">
<div slot="header" class="clearfix" style="cursor: move">
<span>悬浮窗</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="toggle">{{ show ? '收起' : '展开'}}</el-button>
</div>
<transition name="ul">
<div v-if="show" class="ul-box">
<span> {{user}} </span>
</div>
</transition>
</el-card>
</div>
`,
data: function() {
return {
show: true,
list: [],
user: {
username: '',
follow: 0,
title: '',
view: 0,
},
};
},
mounted() {},
methods: {
toggle() {
this.show = !this.show;
},
},
});
}
因为只能在js中编写vue组件,所以得用template模板,同时使用了directives,给组件添加了拖拽的功能(尤其是window.onmousemove,如果是元素绑定他自身的鼠标移动事件,那么拖拽鼠标将会十分卡顿),还使用了transition来进行缓慢动画效果其中注入的css代码如下
.float-page {
width: 400px;
border-radius: 8px;
position: fixed;
left: 50%;
top: 25%;
z-index: 1000001;
} .el-card__header {
padding: 10px 15px !important
} .ul-box {
height: 200px;
overflow: hidden;
} .ul-enter-active,
.ul-leave-active {
transition: all 0.5s;
}
.ul-enter,
.ul-leave-to {
height: 0;
}

相关逻辑可自行观看,这里不在赘述了,并不复杂。

也顺带是复习一下HTML中鼠标事件和vue自定义命令了

功能实现

主要功能

  • 检测视频页面,输出对应up主,关注数以及视频标题播放(参数过多就不一一显示了)

  • 监控关键词根据内容判断是否点赞,例如文本出现了下次一定,那么就点赞。

输出相关信息

这个其实只要接触过一丢丢爬虫的肯定都会知道如何实现,通过右键审查元素,像这样

然后使用dom操作,选择对应的元素,输出便可

> document.querySelector("#v_upinfo > div.up-info_right > div.name > a.username").innerText
< '老番茄'

当然使用JQuery效果也是一样的。后续我都会使用JQuery来进行操作

在src/content-script/bilibili.js中写下如下代码

window.onload = function() {
console.log('加载完毕'); function getInfo() {
let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text();
let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
let title = $(`#viewbox_report > h1 > span`).text();
let view = $('#viewbox_report > div > span.view').attr('title'); console.log(username, follow, title, view);
} getInfo();
};

重新加载插件,然后输出查看结果

加载完毕
bilibili.js:19 老番茄 1606.0万 顶级画质 总播放数2368406

这些数据肯定单纯的输出肯定是没什么作用的,要能显示到内嵌悬浮窗口,或者是popup页面上(甚至发送ajax请求到远程服务器上保存)

对上面代码微改一下

window.onload = function() {
console.log('加载完毕'); function getInfo() {
let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text().trim()
let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
let title = $(`#viewbox_report > h1 > span`).text();
let view = $('#viewbox_report > div > span.view').attr('title'); //console.log(username, follow, title, view);
window.kz_vm.user = {
username,
follow,
title,
view,
}; }
getInfo();
};
其中window.kz_vm是通过window.kz_vm = new Vue() 初始化的,方便我们操作vm对象,就需要通过jquery选择元素在添加属性了。如果你想的话也可以直接在content-script.js上编写代码,这样就无需使用window对象,但这样导致一些业务逻辑都堆在一个文件里,所以我习惯分成bilibili.js 然后注入方式为document_end,然后在操作dom元素吗,实现效果如下

如果像显示到popup页面只需要通过页面通信就行了,不过前提得先popup打开才行,所以一般都是通过background来进行中转,一般来说很少 content –> popup(因为操作popup的前提都是popup要打开),相对更多的是content –> background 或 popup –> content

实现评论

这边简单编写了一下页面,通过popup给content,让content输入评论内容,与点击发送,先看效果

同样的,找到对应元素位置

// 评论文本框
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val("要回复的内容");
// 评论按钮
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();

接着就是写页面通信的了,可以看到是popup向content发送请求

window.onload = function() {
console.log('content加载完毕'); function comment() {
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
let { cmd, message } = request;
if (cmd === 'addComment') {
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val(message);
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();
} sendResponse('我收到了你的消息!');
});
} comment();
};
<template>
<div>
<el-container>
<el-header height="24">B站小工具</el-header>
<el-main>
<el-row :gutter="5">
<el-input
type="textarea"
:rows="2"
placeholder="请输入内容"
v-model="message"
class="mb-5"
>
</el-input> <div>
<el-button @click="addComment">评论</el-button>
</div>
</el-row>
</el-main>
</el-container>
</div>
</template> <script>
export default {
name: 'App',
data() {
return {
message: '',
list: [],
open: false,
}
},
created() {
chrome.storage.sync.get('list', (obj) => {
this.list = obj['list']
})
},
mounted() {
chrome.runtime.onMessage.addListener(function (
request,
sender,
sendResponse
) {
console.log('收到来自content-script的消息:')
console.log(request, sender, sendResponse)
sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request))
})
},
methods: {
sendMessageToContentScript(message, callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
if (callback) callback(response)
})
})
},
addComment() {
this.sendMessageToContentScript(
{ cmd: 'addComment', message: this.message },
function () {
console.log('来自content的回复:' + response)
}
)
},
},
}
</script>

代码就不解读了,调用sendMessageToContentScript方法即可。相关源码可自行下载查看

实现类似点赞功能也是同理的。

整体体验

当时写Chrome插件的效率不能说慢,反正不快就是了,像一些tips,都得自行封装。用过Vue的都知道写网页很方便,写Chrome插件未尝不是编写一个网页,当时的我在接触了Vue后就萌发了使用vue来编写Chrome的想法,当然肯定不止我一个这么想过,所以我在github上就能搜索到相应的源码,于是就有了这篇文章。

如果有涉及到爬取数据相关的,我肯定是首选使用HTTP协议,如果在搞不定我会选择使用puppeteerjs,不过Chrome插件主要还是增强页面功能的,可以实现原本页面不具备的功能。

本文仅仅只是初步体验,简单编写了个小项目,后期有可能会实现一个百度网盘一键填写提取码,Js自吐Hooke相关的。(原本是打算做pdd商家自动回复的,客户说要用客户端而不是网页端(客户端可以多号登陆),无奈,这篇博客就拿B站来演示了)

本文转载于:

https://juejin.cn/post/7009128182007742495

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--使用Vue开发Chrome插件的更多相关文章

  1. 使用 Vuejs 开发 chrome 插件的注意事项

    使用 Vuejs 开发 chrome 插件 chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 ch ...

  2. 使用Vuejs 开发chrome 插件的注意事项

    chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 chrome 插件可以提高我们的开发效率,甚至方 ...

  3. vue开发chrome扩展,数据通过storage对象获取

    开发chrome插件时遇到一个问题,那就是单文件组件的data数据需要从chrome提供的storage对象中获取,但是 chrome.storage.sync.get 方法是异步获取数据的,需要通过 ...

  4. 自己开发chrome插件生成二维码

    摘要: 最近在开发微信项目时,需要在微信调试,所以经常会在微信中输入本地服务地址,输入起来特别麻烦,所以自己就想了想微信中的扫一扫,然后开发了这款chrome插件,将当前url生成二维码,用微信扫一扫 ...

  5. 开发chrome插件(扩展)

    官方文档 https://developer.chrome.com/extensions/getstarted.html [干货]Chrome插件(扩展)开发全攻略 http://blog.haoji ...

  6. 替代Infinity绝佳的自主开发chrome插件

    最近闲来无事在好朋(da)友(shen)的帮助下开发一个chrome插件,目的是为了替换infinity主页插件, 当然在此也推荐一波infinity确实不错,界面和易用性都是非常好用的水准了. 主页 ...

  7. blazor wasm开发chrome插件

    用blazor(Wasm)开发了一个chrome插件感觉效率挺高的,分享给大家 先简单介绍下WebAssembly的原理: "WebAssembly是一种用于基于堆栈的虚拟机的二进制指令格式 ...

  8. 开发Chrome插件,实现网站自动登录

    近期被一个事情困扰着,我们采购了一款软件,里面有一个数据大屏页,当登录过期后,数据就会保持原状,不再更新.和供应商反馈了很多次,都无法彻底解决数据显示的问题,没办法,自己周末在家研究,网站自动登录的事 ...

  9. 试着开发chrome插件

    我的第一个chrome插件,是app形式的 代码如下 创建一个文件: 1.manifest.json { "version": "1.0", "man ...

  10. 使用Python开发chrome插件

    本文由 伯乐在线 - xianhu 翻译,Daetalus 校稿.未经许可,禁止转载!英文出处:pythonspot.com.欢迎加入翻译小组. 谷歌Chrome插件是使用HTML.JavaScrip ...

随机推荐

  1. JS leetcode 寻找数组的中心索引 题解分析

    壹 ❀ 引 今天是的题目来自leetcode的724. 寻找数组的中心索引,做完之后我感觉自己像个憨憨,题目描述如下: 给定一个整数类型的数组 nums,请编写一个能够返回数组"中心索引&q ...

  2. od命令

    od命令 od命令会读取所指定的文件的内容,并将其内容以八进制字节码呈现出来. 语法 od [OPTION]... [FILE]... od [-abcdfilosx]... [FILE] [[+]O ...

  3. ftp 出现Passive mode refused 解决办法

    在shell中调用FTP出现下面错误时, Permission denied. Passive mode refused. Permission denied. Passive mode refuse ...

  4. Maven如何打包可执行jar包

    假设我有一个maven项目叫:hello-world 新建一个HelloWorld类: package com.dylan.mvnbook.helloworld; public class Hello ...

  5. Java并发编程实例--19.在一个锁中使用多个条件

    一个锁可能关联了一个或多个条件.这些条件可以在Condition接口中声名. 使用这些条件的目的是去控制一个锁并且可以检查一个条件是true或false,如果为false,则暂停直到 另一个线程来唤醒 ...

  6. 我的小程序之旅七:微信公众号设置IP白名单

    一.为什么要配置IP白名单 此处IP为服务器对公网IP: 在IP白名单内的IP地址作为来源,获取access_token接口才可调用成功. 而想要调用公众号相关API,就必须获取access_toke ...

  7. win32 - 找出占用文件的进程id和name

    日常文件操作的时候,在删除或者移动某个文件的时候,发现它被某些进程占用了. 那么下面的代码就可以帮助我们找出这些进程的id和name. 原理: 将资源注册到Restart Manager会话.重新启动 ...

  8. python列表操作的大O效率

  9. ubuntu 中 docker 每次都输入 sudo 命令

    查看用户组及成员 sudo cat /etc/group | grep docker 可以添加docker组 sudo groupadd docker 添加用户到docker组 sudo gpassw ...

  10. 【Java复健指南11】OOP高级02-代码块、单例设计和final关键字

    代码块 定义 代码化块又称为初始化块,属于类中的成员[即是类的一部分]. 类似于方法,将逻辑语句封装在方法体中,通过{}包围起来. 但和方法不同,没有方法名,没有返回,没有参数,只有方法体, 而且不用 ...