通过这篇文章你能够学习到:
- 快速使用油猴插件进行浏览器插件开发
- 用wokko脚手架开发1个有意思的插件: 划词搜索MoveSearch
- 有精力的话可以继续学习wokoo脚手架的搭建。
为什么要学习浏览器插件开发
浏览器插件开发有的同学可能接触的不多,或者有疑问,学习开发浏览器插件有什么用呢?这里我列举一些工作中的场景:
场景1:
告警平台收到某个前端项目的告警日志,为了复现定位问题,需要登录某个账号内进行查看。此时rd需要联系产品同学,让产品同学联系对应的用户,让用户提供账号密码给rd。此流程很长,而且给用户造成不好的印象。而如果公司内部支持免密登录,只是需要拼接一个特定的url,url规则比较复杂,需要有账号id等信息。此时如果将拼接规则写到插件里,插件提供一个按钮就能够跳转到对应的用户账号中,是不是降低了rd的工作量?
场景2:
当测试的同学发现问题时,需要截图,打开测试平台,填写具体的问题,提交测试报告。如果能有插件在当前页面中弹出要填写的测试表单,是不是就减少了测试同学的工作流程?
学会了浏览器插件开发后,可以做一些工作场景相关的插件,来帮助自己以及同事提高效率。这不管是述职晋升还是跳槽换工作都会是一个加分的亮点。
正式开始插件开发
一、划词搜索MoveSearch
成品展示
安装地址:
- 安装油猴插件
- 安装MoveSearch
代码地址:wokoo/example/MoveSearch
开发步骤
1.1 项目安装 & 初始化配置
npm i wokoo -g
wokoo MoveSearch
选择模板
- vue
- react
这里选择react
安装完成后,出现安装成功的界面
根据提示执行下命令
cd MoveSearch
npm start
-
打开油猴脚本编辑器,把tampermonkey.js的内容复制进去。
-
打开网页开发者搜索,右上角出现猴子的logo,说明环境跑通
1.2 实现基本功能
先整理一下思路,要实现划词搜索功能,主要实现以下步骤:
- 监听mouseup事件,触发时检查是否有文字被选中,如果有则弹窗展示
- 弹窗展示前发请求到baidu的开发者搜索的接口,获取搜索内容
- 如果搜索内容不为空,将内容展示到弹窗中
- 边界检测,点击弹窗区域外的内容,弹窗关闭
此处有个难点,就是baidu的开发者搜索接口不允许跨域,比如在掘金的网页中去请求开发者搜索的接口会被拦住。为解决这个问题,我使用vercel提供的serverless服务做了一次代理,在代理中给响应头增加了Access-Control-Allow-Origin
等字段,开启跨域。具体方法在1.3给出,此时我们先在开发者搜索页面里面进行开发,先保证是同域下。
步骤1. 监听mouseup事件,触发时检查是否有文字被选中
编辑/src/app.js
文件,增加对mouseup的监听:
import React from 'react'
import axios from 'axios'
import './app.less'
export default class extends React.Component {
constructor(props) {
super(props)
this.state = { show: false }
}
componentDidMount() {
document.addEventListener('mouseup', (e) => {
var selectionObj = window.getSelection()
var selectedText = selectionObj.toString()
console.log('selectedText', selectedText)
})
}
render() {
let { show } = this.state
return <>{show ? <div className="Wokoo"></div> : null}</>
}
}
步骤2. 弹窗展示前发请求到开发者搜索的接口,获取搜索内容
- 安装axios,引入axios
- 对componentDidMount内的代码进行改造,请求开发者搜索 获取搜索结果
componentDidMount() {
document.addEventListener('mouseup', (e) => {
var selectionObj = window.getSelection()
var selectedText = selectionObj.toString()
console.log('selectionObj::', selectedText)
if (selectedText.length === 0) {
} else {
axios
.get(
`https://kaifa.baidu.com/rest/v1/search?query=${selectedText}&pageNum=1&pageSize=10`
)
.then((res) => {
let { data } = res.data.data.documents
console.log(data)
if (data.length) {
this.setState({
data: data,
show: true,
})
}
})
}
})
}
- 修改render,展示搜索结果
render() {
let { show, data } = this.state
return (
<>
{show ? (
<div className="Wokoo">
<ul>
{data.map((i) => (
<li>{i.title}</li>
))}
</ul>
</div>
) : null}
</>
)
}
此时,网页中能够看到搜索的结果了,基本功能搞定了。
步骤3. 计算弹窗出现的位置,让弹窗出现在选中文字下方
我们可以得出计算公式为:
此时,app.js
的代码被改造成这样:
import React from 'react'
import axios from 'axios'
import './app.less'
const MODAL_WIDTH = 350
export default class extends React.Component {
constructor(props) {
super(props)
this.state = { show: false, data: [] }
}
componentDidMount() {
document.addEventListener('mouseup', (e) => {
var selectionObj = window.getSelection()
var selectedText = selectionObj.toString()
if (selectedText.length === 0) {
} else {
var selectionObjRect = selectionObj
.getRangeAt(0)
.getBoundingClientRect()
let { x, y, height, width } = selectionObjRect // 获取选中文字的位置,x y是横纵坐标,height width是选中文字的高度和宽度
// 计算弹窗位置,算出left和top
var left = x - MODAL_WIDTH / 2 + width / 2
left = left > 10 ? left : 10
var top = y + height
var scrollLeft =
document.documentElement.scrollLeft || document.body.scrollLeft
var scrollTop =
document.documentElement.scrollTop || document.body.scrollTop
axios
.get(
`https://kaifa.baidu.com/rest/v1/search?query=${selectedText}&pageNum=1&pageSize=10`
)
.then((res) => {
let { data } = res.data.data.documents
if (data.length) {
this.setState({
data,
show: true,
selectedText: selectedText,
modalPosition: {
left: left + scrollLeft,
top: top + scrollTop,
},
})
}
})
}
})
}
render() {
let { show, selectedText, modalPosition, data } = this.state
return (
<>
{show && data && data.length ? (
<div
className="move-search"
id="MoveSearchApp"
style={{
...modalPosition,
}}
>
<div className="move-search-content">
<ul className="move-search-ul">
{data.map((l) => (
<li className="move-search-li" key={l.id}>
<a href={l.url} target="_blank">
{l.title}
</a>
<span>{l.summary}</span>
</li>
))}
</ul>
</div>
<div className="move-search-bottom-fade"></div>
<footer className="move-search-footer">
<a
href={`https://kaifa.baidu.com/searchPage?wd=${selectedText}`}
target="_blank"
>
Read More
</a>
</footer>
</div>
) : null}
</>
)
}
}
别忘了增加样式,修改app.less
.move-search {
position: absolute;
text-align: center;
width: 350px;
max-height: 300px;
top: 0;
right: 0;
border-radius: 5px;
z-index: 2147483647;
box-shadow: rgba(0, 0, 0, 0.2) 0px 16px 100px 0px;
transition: all 0.1s ease-out 0s;
border: 1px solide #282a33;
background: #fff;
font-size: 12px;
overflow: scroll;
text-align: left;
}
步骤4. 边界检测,点击弹窗外部,弹窗隐藏
边界检测的判断条件如下:
function boundaryDetection(x, y, modalPosition = { left: 0, top: 0 }) {
let { left, top } = modalPosition
if (
x > left &&
x < left + MODAL_WIDTH &&
y > top &&
y < top + MoveSearchApp.offsetHeight
) {
return true
}
return false
}
修改app.js
增加弹窗消失的逻辑,在selectedText为空时判断鼠标位置是否在弹窗内。
import React from 'react'
import axios from 'axios'
import './app.less'
// 弹窗宽度
const MODAL_WIDTH = 350
/**
* 边界检测,鼠标点击modal之外,modal隐藏
* @param {*} x 鼠标的x轴位置
* @param {*} y 鼠标的y轴位置
* @param {*} modalPosition 弹窗的left和top
*/
function boundaryDetection(x, y, modalPosition = { left: 0, top: 0 }) {
let { left, top } = modalPosition
if (
x > left &&
x < left + MODAL_WIDTH &&
y > top &&
y < top + MoveSearchApp.offsetHeight
) {
return true
}
return false
}
export default class extends React.Component {
constructor(props) {
super(props)
this.state = { show: false, data: [] }
}
componentDidMount() {
document.addEventListener('mouseup', (e) => {
var selectionObj = window.getSelection()
var selectedText = selectionObj.toString()
if (selectedText.length === 0) {
if (this.state.show) {
// 重新计算是否关闭弹窗
// 检测鼠标位置是否在弹窗内,不是则关闭弹窗
var inModal = boundaryDetection(
e.clientX,
e.clientY,
this.state.modalPosition
)
if (!inModal) {
this.setState({
show: false,
data: [],
})
}
}
} else {
var selectionObjRect = selectionObj
.getRangeAt(0)
.getBoundingClientRect()
let { x, y, height, width } = selectionObjRect // 获取选中文字的位置,x y是横纵坐标,height width是选中文字的高度和宽度
// 计算弹窗位置,算出left和top
var left = x - MODAL_WIDTH / 2 + width / 2
left = left > 10 ? left : 10
var top = y + height
var scrollLeft =
document.documentElement.scrollLeft || document.body.scrollLeft
var scrollTop =
document.documentElement.scrollTop || document.body.scrollTop
axios
.get(
`https://kaifa.baidu.com/rest/v1/search?query=${selectedText}&pageNum=1&pageSize=10`
)
.then((res) => {
let { data } = res.data.data.documents
if (data.length) {
this.setState({
data,
show: true,
selectedText: selectedText,
modalPosition: {
left: left + scrollLeft,
top: top + scrollTop,
},
})
}
})
}
})
}
render() {
let { show, selectedText, modalPosition, data } = this.state
return (
<>
{show && data && data.length ? (
<div
className="move-search"
id="MoveSearchApp"
style={{
...modalPosition,
}}
>
<div className="move-search-content">
<ul className="move-search-ul">
{data.map((l) => (
<li className="move-search-li" key={l.id}>
<a href={l.url} target="_blank">
{l.title}
</a>
<span>{l.summary}</span>
</li>
))}
</ul>
</div>
<div className="move-search-bottom-fade"></div>
<footer className="move-search-footer">
<a
href={`https://kaifa.baidu.com/searchPage?wd=${selectedText}`}
target="_blank"
>
Read More
</a>
</footer>
</div>
) : null}
</>
)
}
}
此时,项目的基本功能已经开发完成?,一些样式问题可以自行调整。
具体的代码逻辑可看github地址:MoveSearch
1.3 解决跨域问题
百度的接口不允许跨域访问。也就是说如果我们在一个第三方页面,比如cdn,掘金等的网页里,要访问百度的接口就存在跨域限制,没法访问。我采用增加node中间层,在node中给响应增加Access-Control-Allow-Origin
请求头的方式开启跨域。
目前有现成的提供serverless服务的第三方厂家,提供服务器,node环境等服务,我们只要关心node的代码逻辑即可,部署服务器配置环境等问题交给第三方厂家解决。我采用vercel提供的服务。
下面的内容和插件开发关系不大,如果不感兴趣的同学可以直接使用我配置好的域名:https:/movesearch.vercel.app/api/baidu
。
也就是将代码中axios请求的url由
`https://kaifa.baidu.com/rest/v1/search?query=${selectedText}&pageNum=1&pageSize=10`
替换成
`https:/movesearch.vercel.app/api/baidu?query=${selectedText}&pageNum=1&pageSize=10`
如果对于如何实现跨域配置感兴趣的话,可以继续往下看?。
- 登录vercel官网,根据提示绑定github账号;
- 点击新建next项目。vercel会自动给你的github上创建新的仓库,并有一个初始化项目;
- git clone 此项目到本地,根据readme的提示,执行
npm run dev
,启动项目 - 增加
src/api/baidu.js
文件,内容如下:
稍微解释一下,src/api/baidu.js
下的文件对应的就是/api/baidu
接口
const { createProxyMiddleware } = require('http-proxy-middleware')
// restream parsed body before proxying
var restream = function (proxyRes, req, res, options) {
proxyRes.headers['Access-Control-Allow-Origin'] = '*'
proxyRes.headers['Access-Control-Allow-Headers'] = '*'
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
}
const apiProxy = createProxyMiddleware({
target: 'https://kaifa.baidu.com/',
changeOrigin: true,
pathRewrite: { '^/api/baidu': '/rest/v1/search' },
secure: false,
onProxyRes: restream,
})
module.exports = function (req, res) {
apiProxy(req, res, (result) => {
console.log('result:', result)
if (result instanceof Error) {
throw result
}
throw new Error(
`Request '${req.url}' is not proxied! We should never reach here!`
)
})
}
具体的代码逻辑可看github地址:nextjs
wokoo脚手架的搭建
感兴趣的同学可以阅读 wokoo脚手架(搭建篇)
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!