记录--使用Vue开发Chrome插件
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
环境搭建
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;
},
},
});
}
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给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插件的更多相关文章
- 使用 Vuejs 开发 chrome 插件的注意事项
使用 Vuejs 开发 chrome 插件 chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 ch ...
- 使用Vuejs 开发chrome 插件的注意事项
chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 chrome 插件可以提高我们的开发效率,甚至方 ...
- vue开发chrome扩展,数据通过storage对象获取
开发chrome插件时遇到一个问题,那就是单文件组件的data数据需要从chrome提供的storage对象中获取,但是 chrome.storage.sync.get 方法是异步获取数据的,需要通过 ...
- 自己开发chrome插件生成二维码
摘要: 最近在开发微信项目时,需要在微信调试,所以经常会在微信中输入本地服务地址,输入起来特别麻烦,所以自己就想了想微信中的扫一扫,然后开发了这款chrome插件,将当前url生成二维码,用微信扫一扫 ...
- 开发chrome插件(扩展)
官方文档 https://developer.chrome.com/extensions/getstarted.html [干货]Chrome插件(扩展)开发全攻略 http://blog.haoji ...
- 替代Infinity绝佳的自主开发chrome插件
最近闲来无事在好朋(da)友(shen)的帮助下开发一个chrome插件,目的是为了替换infinity主页插件, 当然在此也推荐一波infinity确实不错,界面和易用性都是非常好用的水准了. 主页 ...
- blazor wasm开发chrome插件
用blazor(Wasm)开发了一个chrome插件感觉效率挺高的,分享给大家 先简单介绍下WebAssembly的原理: "WebAssembly是一种用于基于堆栈的虚拟机的二进制指令格式 ...
- 开发Chrome插件,实现网站自动登录
近期被一个事情困扰着,我们采购了一款软件,里面有一个数据大屏页,当登录过期后,数据就会保持原状,不再更新.和供应商反馈了很多次,都无法彻底解决数据显示的问题,没办法,自己周末在家研究,网站自动登录的事 ...
- 试着开发chrome插件
我的第一个chrome插件,是app形式的 代码如下 创建一个文件: 1.manifest.json { "version": "1.0", "man ...
- 使用Python开发chrome插件
本文由 伯乐在线 - xianhu 翻译,Daetalus 校稿.未经许可,禁止转载!英文出处:pythonspot.com.欢迎加入翻译小组. 谷歌Chrome插件是使用HTML.JavaScrip ...
随机推荐
- HWS山大专区PWN双一血 & CRYPTO-WP
2023.11.18 两天半的比赛,就打了半天(因为要赶去打香山杯决赛了),不过结果还算好,人生第一次拿了两个一血hhh.写wp的时候人在中大南校北门的酒店里:) controller 格式化字符串泄 ...
- 从零开始的 dbt 入门教程 (dbt core 开发进阶篇)
引 在上一篇文章中,我们花了专门的篇幅介绍了 dbt 更多实用的命令,那么我们继续按照之前的约定来聊 dbt 中你可能会遇到的疑惑以及有用的概念,如果你是 dbt 初学者,我相信如下知识点一定会对你有 ...
- 从零开始手写缓存框架(12)redis expire 过期的随机特性详解及实现
前言 java从零手写实现redis(一)如何实现固定大小的缓存? java从零手写实现redis(二)redis expire 过期原理 java从零手写实现redis(三)内存数据如何重启不丢失? ...
- 使用winhex查看FAT16格式结构
winhex介绍 winhex可以直接查看磁盘二进制信息, 可以比较直观地查看到各种文件系统格式的区别. winhex使用 查看硬盘要管理员权限, 即启动的时候要用邮件管理员权限启动 点击Tools- ...
- 【Android】使用 ContentObserver 监控统状态信息
1 前言 使用ContentProvider实现跨进程通讯 中介绍了自定义 ContentProvider,为外界提供操作 SQLite 的接口.但是大多数情况下,服务端的 ContentProvid ...
- js与java使用AES加密算法实现前后端加密解密
AES加密算法入门:https://blog.csdn.net/IndexMan/article/details/87284833 第三方crypto.js下载地址:https://download. ...
- 尝试通过uniapp仿微信页面
最近一直想弄一个app,然后刚好看到Uniapp这个技术,然后最近就用几个晚上琢磨了下: 先看下成果: 1.通讯页面,这个是通过插件uni-indexed-list 索引列表 进行改造过后:改造过程还 ...
- 【LeetCode二叉树#05】平衡二叉树
力扣题目链接(opens new window)](https://leetcode.cn/problems/balanced-binary-tree/) 给定一个二叉树,判断它是否是高度平衡的二叉树 ...
- 【LeetCode字符串#04】左旋转字符串,以及反转函数使用说明
左旋转字符串 力扣题目链接(opens new window) 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部.请定义一个函数实现字符串左旋转操作的功能.比如,输入字符串"a ...
- SUB-LVDS 与LVDS 互联
SUB-LVDS 与 LVDS介绍 电气规范 今天有同学问SUB-LVDS输出是否能接到LVDS输入上,以前没用过SUB-LVDS,一起学习一下. Sub-LVDS is a differential ...