整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Chrome 插件開發入門(一)

          Chrome 插件開發入門(一)

          前言

          關于Chrome插件,大家可能或多或少的都會使用幾個,比如 廣告屏蔽插件AdBlock,大名鼎鼎的腳本管理插件 Tampermonkey(油猴)等,這些插件給予了我們日常使用瀏覽器很大的方便。那如何開發一個自己的插件呢,最近我研究了Google插件開發文檔和網絡上的一些文檔,整理了開發筆記。由于Google在推廣V3版本的插件,這里就使用最新的V3版本做研究。

          1 插件結構概述

          在開發插件之前,先了解一下Chrome插件的結構和基本概念是很有必要的。下面就介紹一下幾個基本概念。

          1.1 Chrome插件的結構



          alt Architecture

          1.2 基本概念

          1.2.1 Manifest

          Manifest(manifest.json)Chrome 插件的配置文件,類似于前段工程中的 webpack.config.js或者后端maven項目中的pom.xml。 它是一個json文件,必須位于插件項目的根目錄中,而且名稱必須是 manifest.jsonManifest 記錄著重要的元數據,定義資源,聲明權限,并標識哪些文件在后臺和頁面上運行。下面列出一些重要的配置項。全部配置項可以查看官方文檔: https://developer.chrome.com/docs/extensions/mv3/manifest/

          {
            // 必須項
            "manifest_version": 3, // 版本, V3是Chrome最新插件版本,類似前段node的版本或后端jdk版本
            "name": "My Extension", // 插件名稱
            "version": "1.0.1",     // 插件版本
          
            // 推薦項目
            "action": {...},  // 用于點擊圖標彈出框,對于彈出框接受的是html文件
            "description": "A plain text description", // 插件描述
            "icons": {...}, // 插件圖標
          
            // 可選項(重要, 可選不表示不重要)
            "author": "developer@example.com",  // 插件作者
            "commands": {...}, // 使用命令 API 添加觸發擴展中操作的鍵盤快捷鍵
            "background": {...}, // Service Worker 配置
            "content_scripts": [{...}], // 內容腳本配置
            "options_page": "options.html", // options_page 配置
            "permissions": ["..."], // 插件需要使用的權限
            "version_name": "1.0 beta",
            "web_accessible_resources": [...] // Web 可訪問資源配置
          }
          

          1.2.2 Service Worker

          Service Worker 是一個事件處理器,它可以監聽瀏覽器事件,例如導航到新頁面、刪除書簽或關閉選項卡等,一旦這些事件觸發,則 Service Worker 中注冊的事件處理器就會被調用。需要明確的是,這里的事件是瀏覽器的事件,并不是頁面文檔中js的事件(例如, 鼠標點擊事件,滑動事件)。Service Worker 有獨立的運行環境,和頁面js的運行環境是隔離的,所以它不能訪問頁面的DOM,但是它可是使用Chrome的API

          1.2.3 Content Script

          Content Script 又叫 ”內容腳本“, 它運行在網頁上下文中,能夠對DOM進行訪問和修改,或者將頁面信息傳遞給插件的其他部分,如 Service WorkerPopup Page。雖然 Content Script 運行在網頁上下文中,但是它和頁面引入的js運行環境是隔離的,也就是說,它不能訪問頁面定義的變量和方法。

          1.2.4 Popup Page

          Popup Page 又叫”彈出頁“, 是點擊插件圖標是彈出的頁面,如下圖:

          它和普通web頁面很相似,可以有自己的jscss,但是不允許內聯js代碼。 彈出頁和普通web不同的地方是,彈出頁的js可是使用ChromeAPI,但是它和內容腳本一樣,不能訪問普通頁面的DOM和普通頁面引入js。

          1.2.5 Options Page

          Options Page 又叫”選項頁“, 顧名思義,它是插件的配置頁面,用戶在該頁面可以對插件進行設置。

          我們先對這些基礎知識有個基本概念,后面會詳細說明它們的使用方式,給出相應的 Demo

          2 入門小插件

          為了讓我們能夠快速上手開發一個自己的插件,這節我們就開發一個小插件。這個插件功能是和 選詞翻譯 插件功能類似,鼠標選中頁面中的文本,彈出一個提示框,顯示翻譯翻譯后的內容。我們這個插件功能是頁面選中文本,點擊鼠標右鍵彈出選中文本,不做翻譯的功能。 效果演示

          2.1 插件項目結構

          └── src
              ├── icons
              │   └── logo.png
              ├── manifest.json
              └── scripts
                  ├── content.js
                  └── lib
                      └── jQuery.js
          

          我們插件項目結構和 Chrome 官方項目結構保持一致:

          • icons 存放插件圖標
          • manifest.json 插件清單,配置文件
          • scripts 存放 content scripts 和 依賴的庫

          2.2 項目代碼

          2.2.1 manifest.json

          {
            "manifest_version": 3,
            "name": "演示插件",
            "version": "1.0.0",
            "description": "這是一個演示插件",
            "icons": {
              "16": "/icons/logo.png",
              "32": "/icons/logo.png",
              "48": "/icons/logo.png",
              "128": "/icons/logo.png"
            },
            "content_scripts": [
              {
                "matches": [
                  "https://*/*",
                  "http://*/*",
                  "file:///*"
                ],
                "js": [
                  "scripts/lib/jQuery.js",
                  "scripts/content.js"
                ],
                "run_at": "document_idle"
              }
            ]
          }
          

          manifest.json 主要關心一下配置項:

          • manifest_version 這是使用的V3版本。
          • name 給插件起個名字。
          • icons 插件圖標。
          • content_scripts 配置內容腳本怎么運行。 matches 匹配路徑。內容腳本在那個網站路徑頁面上運行。 js 運行那些內容腳本。需要注意js之間的依賴關系,被依賴的js放在前面。這里 content.js 使用了 jQuery, 所以jQuery 引入位置在 content.js之前。 run_at 腳本注入時機。取值有 document_start, document_end, document_idle, 默認是 document_idledocument_start: 在構建DOM和運行頁面js之前注入。document_end: 在DOM構建完成后,圖片和frames加載之前注入。document_idle: 在 document_endwindow.onload事件之間注入。

          2.2.2 content.js

          /**
           * 顯示提示框
           *
           * @param {*} e 鼠標點擊事件
           * @param {*} tip 顯示的文本內容
           */
          function showTip(e, tip) {
              $('#tipId').html(tip);
              $('#tipDiv').css('left', e.pageX + 'px');
              $('#tipDiv').css('top', e.pageY + 10 + 'px');
              $('#tipDiv').css('display', 'block');
          }
          
          /**
           * 隱藏提示框
           */
          function hiddenTip() {
              $('#tipDiv').css('display', 'none');
          }
          
          /**
           * 初始化事件監聽器
           */
          function initEventListeners() {
          
              // 監聽鼠標按下事件,關閉提示框
              document.addEventListener("mousedown", ()=> {
                  hiddenTip();
              });
          
              // 在提示框內點擊鼠標,阻止提示框關閉
              $('#tipDiv').bind("mousedown", (e)=> {
                  e.stopPropagation();
              })
          
              // 點擊鼠標右鍵,彈出選擇的內容
              document.oncontextmenu=(e)=> {
                  // 獲取選中的內容
                  const selected=window.getSelection().toString();
                  if (!selected) {
                      return true;
                  }
          
                  // 顯示選中的內容
                  showTip(e, selected);
                  return false;
              }
          }
          
          (()=> {
              // 頁面添加一個提示的div(tipDiv), 用于彈出提示框
              $('body').append("<div id='tipDiv' style='position:absolute; float: left; z-index:1000; left: 0px; top: 0px; display: none; width: 600px; height: 100px;'></div>")
          
              // style 太長了,分別設置css屬性
              $('#tipDiv').css('background-color', 'lightsteelblue');
              $('#tipDiv').css('font-size', '200%');
          
              // 在 tipDiv 中添加一個 內容div, 用于放提示內容
              $('#tipDiv').append("<div id='tipId'>測試彈出頁面</div>");
          
              // 初始化事件監聽器
              initEventListeners();
          })();
          

          content.js的代碼注釋寫的很清楚了,這里就不重復解釋代碼了。

          2.2.3 安裝插件

          3 總結

          本文先介紹 Chrome 插件開發過程中必備的基本概念,然后通過寫一個小Demo,來說明如何開發一個 Chrome 插件,幫助大家快速入門。后面會再出進階文章,對 Content ScriptService WorkerPopup Page 等做進一步研究。

          eact Hooks已經出了有段時間了,不知道大伙有沒有嘗試著去用過,下面小編帶大伙對比看下三大基礎 Hooks 和傳統 class 組件的區別和用法。

          我們所指的三個基礎 Hooks 是:

          • useState 在函數式組件內維護 state
          • useEffect 函數式組件內有副作用的調用與 componentDidMount、componentDidUpdate 類似但又有所區別
          • useContext 監聽 provider 更新變化

          useState

          useState 允許我們在函數式組件中維護 state,傳統的做法需要使用類組件。舉個例子:我們需要一個輸入框,隨著輸入框內容的改變,組件內部的 label 標簽顯示的內容也同時改變。下面是兩種不同的寫法:

          不使用 useState

          import React from "react";
          // 1
          export class ClassTest extends React.Component {
           // 2
           state={
           username: this.props.initialState
           }
           // 3
           changeUserName(val) {
           this.setState({ username: val })
           }
           // 4
           render() {
           return (
           <div>
           <label style={{ display: 'block' }} htmlFor="username">username: {this.state.username}</label>
           <input type="text" name="username" onChange={e=> this.changeUserName(e.target.value)} />
           </div>
           )
           }
          }
          
          1. 我們需要定義一個類并從 React.Component 處繼承
          2. 還需要初始化一個 state
          3. 初始化改變 state 的方法
          4. 最后還要使用 render 函數返回 JSX

          使用 useState

          // 1
          import React, { useState } from "react";
          export function UseStateTest({ initialState }) {
           // 2
           let [username, changeUserName]=useState(initialState)
           // 3
           return (
           <div>
           <label style={{ display: 'block' }} htmlFor="username">username: {username}</label>
           <input type="text" name="username" onChange={e=> changeUserName(e.target.value)} />
           </div>
           )
          }
          

          在父組件中使用:

          import React from "react";
          // 引入組件
          import { UseStateTest } from './components/UseStateTest'
          // 4
          const App=()=> (
           <div>
           <UseStateTest initialState={'initial value'} />
           </div>
          )
          export default App;
          
          1. 需要從 react 中引入 useState 這個包
          2. 使用 useState 方法,接收一個初始化參數,定義 state 變量,以及改變 state 的方法
          3. 在需要使用的地方直接使用 state 這個變量即可,增加事件處理函數觸發改變 state 的方法
          4. 在父組件中調用,通過 props 傳遞 initialState 初始化值

          用 useState 方法替換掉原有的 class 不僅性能會有所提升,而且可以看到代碼量減少很多,并且不再需要使用 this,所以能夠維護 state 的函數式組件真的很好用

          useEffect

          useEffect 是專門用來處理副作用的,獲取數據、創建訂閱、手動更改 DOM 等這都是副作用。你可以想象它是 componentDidMount 和 componentDidUpdate 及 componentWillUnmount 的結合。

          舉個例子,比方說我們創建一個 div 標簽,每當點擊就會發送 http 請求并將頁面 title 改為對應的數值:

          import React from 'react'
          // 1
          import { useState, useEffect } from 'react'
          export function UseEffectTest() {
           let [msg, changeMsg]=useState('loading...')
           // 2
           async function getData(url) { // 獲取 json 數據
           return await fetch(url).then(d=> d.json())
           }
           // 3
           async function handleClick() { // 點擊事件改變 state
           let data=await getData('https://httpbin.org/uuid').then(d=> d.uuid)
           changeMsg(data)
           }
           // 4
           useEffect(()=> { // 副作用
           document.title=msg
           })
           return (
           <div onClick={()=> handleClick()}>{msg}</div>
           )
          }
          
          1. 首先從 react 中引入 useEffect
          2. 然后創建獲取數據的 getData 方法
          3. 創建事件處理函數 handleClick
          4. 使用 useEffect 處理副作用:改變頁面的 title

          如果使用傳統的類組件的寫法:

          import React from 'react'
          // 1
          export class ClassTest extends React.Component {
           // 2
           state={
           msg: 'loading...'
           }
           // 3
           async getData(url) { // 獲取 json 數據
           return await fetch(url).then(d=> d.json())
           }
           handleClick=async ()=> { // 點擊事件改變 state
           let data=await this.getData('https://httpbin.org/uuid').then(d=> d.uuid)
           this.setState({ msg: data })
           }
           // 4
           componentDidMount() {
           document.title=this.state.msg
           }
           componentDidUpdate() {
           document.title=this.state.msg
           }
           // 5
           render() {
           return (
           <div onClick={this.handleClick}>{this.state.msg}</div>
           )
           }
          }
          
          1. 首先創建類 ClassTest
          2. 初始化 state
          3. 定義獲取數據方法和事件處理函數
          4. 在 componentDidMount 和 componentDidUpdate 階段改變 document.title
          5. 最后通過 render 函數渲染

          使用 useEffect 不僅去掉了部分不必要的東西,而且合并了 componentDidMount 和 componentDidUpdate 方法,其中的代碼只需要寫一遍。

          第一次渲染和每次更新之后都會觸發這個鉤子,如果需要手動修改自定義觸發規則

          見文檔:https://zh-hans.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

          另外,官網還給了一個訂閱清除訂閱的例子:

          使用 useEffect 直接 return 一個函數即可:

          返回的函數是選填的,可以使用也可以不使用:

          文檔:https://zh-hans.reactjs.org/docs/hooks-effect.html#recap

          比方說我們使用 useEffect 來解綁事件處理函數:

          useEffect(()=> {
           window.addEventListener('keydown', handleKeydown);
           return ()=> {
           window.removeEventListener('keydown', handleKeydown);
           }
          })
          

          useContext

          useContext 的最大的改變是可以在使用 Consumer 的時候不必在包裹 Children 了,比方說我們先創建一個上下文,這個上下文里頭有一個名為 username 的 state,以及一個修改 username 的方法 handleChangeUsername

          創建上下文

          不使用 useState:

          不使用 state hooks 的代碼如下:

          import React, { createContext } from 'react'
          // 1. 使用 createContext 創建上下文
          export const UserContext=new createContext()
          // 2. 創建 Provider
          export class UserProvider extends React.Component {
           handleChangeUsername=(val)=> {
           this.setState({ username: val })
           }
           state={
           username: '',
           handleChangeUsername: this.handleChangeUsername
           }
           render() {
           return (
           <UserContext.Provider value={this.state}>
           {this.props.children}
           </UserContext.Provider>
           ) 
           }
          }
          // 3. 創建 Consumer
          export const UserConsumer=UserContext.Consumer
          

          看看我們做了什么:

          1. 首先我們使用 createContext 創建了上下文
          2. 然后我們創建 Provider;里頭定義了 handleChangeUsername 方法和 username 的 state,并返回一個包裹 this.props.children 的 Provider
          3. 最后我們再返回 UserContext.Consumer

          代碼比較冗長,可以使用上文提到的 useState 對其進行精簡:

          使用 useState:

          使用 state hooks:

          import React, { createContext, useState } from 'react'
          // 1. 使用 createContext 創建上下文
          export const UserContext=new createContext()
          // 2. 創建 Provider
          export const UserProvider=props=> {
           let [username, handleChangeUsername]=useState('')
           return (
           <UserContext.Provider value={{username, handleChangeUsername}}>
           {props.children}
           </UserContext.Provider>
           )
          }
          // 3. 創建 Consumer
          export const UserConsumer=UserContext.Consumer
          

          使用 useState 創建上下文更加簡練。

          使用上下文

          上下文定義完畢后,我們再來看使用 useContext 和不使用 useContext 的區別是啥:

          不使用 useContext:

          import React from "react";
          import { UserConsumer, UserProvider } from './UserContext'
          const Pannel=()=> (
           <UserConsumer>
           {/* 不使用 useContext 需要調用 Consumer 包裹 children */}
           {({ username, handleChangeUsername })=> (
           <div>
           <div>user: {username}</div>
           <input onChange={e=> handleChangeUsername(e.target.value)} />
           </div>
           )}
           </UserConsumer>
          )
          const Form=()=> <Pannel></Pannel>
          const App=()=> (
           <div>
           <UserProvider>
           <Form></Form>
           </UserProvider>
           </div>
          )
          export default App;
          

          使用 useContext:

          只需要引入 UserContext,使用 useContext 方法即可:

          import React, { useContext } from "react"; // 1
          import { UserProvider, UserContext } from './UserContext' // 2
          const Pannel=()=> {
           const { username, handleChangeUsername }=useContext(UserContext) // 3
           return (
           <div>
           <div>user: {username}</div>
           <input onChange={e=> handleChangeUsername(e.target.value)} />
           </div>
           )
          }
          const Form=()=> <Pannel></Pannel>
          // 4
          const App=()=> (
           <div>
           <UserProvider>
           <Form></Form>
           </UserProvider>
           </div>
          )
          export default App;
          

          看看做了啥:

          1. 首先第一步引入useContext
          2. 然后引入 UserProvider 以及上下文 UserContext
          3. 再需要使用的組件中調用 useContext 方法獲取 state
          4. 當然前提是要在父組件中使用 UserProvider 嵌套主 children

          這樣通過 useContext 和 useState 就重構完畢了,看起來代碼又少了不少

          果圖:

          使用場景: 使用React渲染后臺返回的數據, 遍歷以列表的形式展示, 可能內容簡要字段需要鼠標放上去才顯示的

          可以借助DOM的自定義屬性和CSS偽類的attr來實現


          所有代碼:


          主站蜘蛛池模板: 中文字幕一区二区三区有限公司| 日本一区二区三区高清| 国模大胆一区二区三区| 亚洲国产专区一区| 亚洲欧洲一区二区| 亚洲av片一区二区三区| 久热国产精品视频一区二区三区| 精品人妻一区二区三区毛片| 在线|一区二区三区| 一区二区三区在线观看| 亚洲伦理一区二区| 中文字幕AV无码一区二区三区| 好爽毛片一区二区三区四| 国产一区二区精品久久岳√| 日本精品啪啪一区二区三区| 濑亚美莉在线视频一区| 午夜视频一区二区| 久久国产精品免费一区| 制服中文字幕一区二区| 中文字幕一区二区三区视频在线| 一区五十路在线中出| 中文字幕一区二区三区日韩精品 | 国产午夜精品一区二区| 日本一区二区三区在线观看| 亚洲av日韩综合一区在线观看| 无码国产精品一区二区免费模式 | 精品一区二区三区免费观看| 亚洲高清偷拍一区二区三区| 国产免费播放一区二区| 69福利视频一区二区| 无码中文字幕乱码一区| 国产精品无码一区二区在线观一| 久久久久人妻精品一区蜜桃| 中文字幕乱码人妻一区二区三区| 色视频综合无码一区二区三区 | 99热门精品一区二区三区无码| 精品一区二区三人妻视频| 一区二区在线视频观看| 精品永久久福利一区二区| www一区二区www免费| 精品一区二区ww|