這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 環境搭建 Vue Web-Extension - A Web-Extension preset for VueJS (vue-web-extension.netlify.app) npm install -g @vue/cli npm i ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
環境搭建
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站來演示了)