最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 快速上手油猴插件开发(实战篇)

    正文概述 掘金(前端小叶子)   2021-02-05   739

    通过这篇文章你能够学习到:

    1. 快速使用油猴插件进行浏览器插件开发
    2. 用wokko脚手架开发1个有意思的插件: 划词搜索MoveSearch
    3. 有精力的话可以继续学习wokoo脚手架的搭建。

    为什么要学习浏览器插件开发

    浏览器插件开发有的同学可能接触的不多,或者有疑问,学习开发浏览器插件有什么用呢?这里我列举一些工作中的场景:

    场景1:

    告警平台收到某个前端项目的告警日志,为了复现定位问题,需要登录某个账号内进行查看。此时rd需要联系产品同学,让产品同学联系对应的用户,让用户提供账号密码给rd。此流程很长,而且给用户造成不好的印象。而如果公司内部支持免密登录,只是需要拼接一个特定的url,url规则比较复杂,需要有账号id等信息。此时如果将拼接规则写到插件里,插件提供一个按钮就能够跳转到对应的用户账号中,是不是降低了rd的工作量?

    场景2:

    当测试的同学发现问题时,需要截图,打开测试平台,填写具体的问题,提交测试报告。如果能有插件在当前页面中弹出要填写的测试表单,是不是就减少了测试同学的工作流程?

    学会了浏览器插件开发后,可以做一些工作场景相关的插件,来帮助自己以及同事提高效率。这不管是述职晋升还是跳槽换工作都会是一个加分的亮点。

    正式开始插件开发

    一、划词搜索MoveSearch

    成品展示

    快速上手油猴插件开发(实战篇)

    安装地址:

    1. 安装油猴插件
    2. 安装MoveSearch

    代码地址:wokoo/example/MoveSearch

    开发步骤

    1.1 项目安装 & 初始化配置

    npm i wokoo -g
    wokoo MoveSearch
    

    选择模板

    • vue
    • react

    这里选择react

    安装完成后,出现安装成功的界面

    快速上手油猴插件开发(实战篇)

    根据提示执行下命令

    cd MoveSearch
    npm start
    
    • 打开油猴脚本编辑器,把tampermonkey.js的内容复制进去。

      快速上手油猴插件开发(实战篇) 快速上手油猴插件开发(实战篇)

    • 打开网页开发者搜索,右上角出现猴子的logo,说明环境跑通

    1.2 实现基本功能

    先整理一下思路,要实现划词搜索功能,主要实现以下步骤:

    1. 监听mouseup事件,触发时检查是否有文字被选中,如果有则弹窗展示
    2. 弹窗展示前发请求到baidu的开发者搜索的接口,获取搜索内容
    3. 如果搜索内容不为空,将内容展示到弹窗中
    4. 边界检测,点击弹窗区域外的内容,弹窗关闭

    此处有个难点,就是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`
    

    如果对于如何实现跨域配置感兴趣的话,可以继续往下看?。

    1. 登录vercel官网,根据提示绑定github账号;
    2. 点击新建next项目。vercel会自动给你的github上创建新的仓库,并有一个初始化项目;
    3. git clone 此项目到本地,根据readme的提示,执行npm run dev,启动项目
    4. 增加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介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元