整合營銷服務商

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

          免費咨詢熱線:

          electron自定義窗口和右鍵菜單樣式

          electron自定義窗口和右鍵菜單樣式

          # 前言

          electron默認沿用系統UI,并沒有提供很多接口供使用者定制樣式,如果想要完全自定義的樣式,目前我能想到的方案只能是通過前端自定義樣式,然后通過進程通信來實現系統基礎功能:最大/小化、關閉、拖動窗口等。

          效果圖:

          在這里插入圖片描述

          在這里插入圖片描述



          一、窗口自定義

          通過前面系列文章我們可以了解到,窗口是通過實例化BrowserWindow對象創建的,實例化時會傳入一些窗口的參數。

          要實現窗口自定義,就必須把窗口默認樣式都屏蔽。幾個關鍵的參數如下:

          transparent: true,
          backgroundColor:'rgba(0,0,0,0)',
          frame:false,

          參數含義很好理解,窗體透明無邊框,參數詳解請查詢官網。

          把系統自帶的窗體樣式去掉后,我們會得到一個只有主體的窗口,這個主體就是前端(vue)渲染的窗口,我們可以通過控制它,來實現任何樣式的窗口。

          但是這會帶來一個問題,那就是窗口對象提供的很多便捷功能無法使用了,所以如果需要做最大化、最小化、拖動窗口等功能,只能通過進程通信,前端發送指令,主進程接收指令后,完成相應功能。具體原理請參考本系列前面關于進程通信的文章。

          這里簡單列一個示例代碼,以最大化為例:

          //vue代碼部分,在某個dom元素上監聽事件
          <img class="title-icon" src="@/assets/img/maxsize.png"  alt="最大化" @click="handleMaxSize()">
          
           function handleMaxSize(){
                  renderApi.handleMaxSize()
              }
          //preload.js中定義通信的api,下面是我項目中所有渲染進程到主進程的通信
          
          const handleSendPageName=(pageName)=> ipcRenderer.send('send-page-name', pageName) //渲染進程主動到主進程
          const handleMinSize=()=> ipcRenderer.send('send-min-size') //渲染進程主動到主進程
          const handleMaxSize=()=> ipcRenderer.send('send-max-size') //渲染進程主動到主進程
          const handleRestore=()=> ipcRenderer.send('send-restore') //渲染進程主動到主進程
          const handleRelaunch=()=> ipcRenderer.send('user-click-Dock-Icon') //渲染進程主動到主進程
          
          const handleCloseWin=()=>{
              ipcRenderer.send('send-auto-close')
          }
          
          contextBridge.exposeInMainWorld('renderApi', {
              //監聽渲染進程事件
              handleGetStoreFiles,
              handleSendPageName,
              handleMinSize,
              handleMaxSize,
              handleCloseWin,
              handleRestore,
              handleRelaunch
          })
          //主進程main.js中接收對應的通信
               ipcMain.on('send-max-size', ()=> {
                      if(win.isMaximized()){
                          win.unmaximize()
                      }else{
                          win.maximize()
                      }
                  })

          至此,模擬窗口最大化功能的全部過程就打通了。

          二、窗口拖拽功能

          這里值得注意的是,拖拽窗口不止是要配置參數,還要給對應dom元素增加類。

          比如說我想實現拖動類名為“c-drag”的元素時,拖動窗口移動,大致代碼如下:

          <div class="c-drag">
          
           </div>
          .c-drag{
            -webkit-app-region: drag;
          }

          -webkit-app-region: drag是electron提供的css樣式,具體可查詢官網。

          這是一個比較小眾的知識點,網上資料目前較少,這里記錄一下。

          三、不同窗口設置不同大小

          這一部分邏輯略微復雜。

          窗口大小的設置一定是在主進程中設置,如果僅僅依靠vue部分控制顯示區域大小,不顯示區域設置為透明,雖然視覺上可以實現不同的窗體大小,但是這是一種偽實現,因為透明部分只是人眼看不到而已,鼠標點擊、拖拽等功能仍然存在,就會對軟件用戶造成困擾。

          在主進程中設置窗口大小,最重要的就是進入不同頁面時,要主動向主進程發送指令,并告訴主進程,我現在進入登錄頁了,我現在進入正常頁了,我現在進入xx頁……

          主進程接收指令后,根據參數,控制窗口的大小即可。

          在我的項目中,各頁面有一個統一的路由跳轉方法,所以我在跳轉路由后,同時將活躍頁面的name通handleSendPageName發送給主進程。代碼如下:

          function turnToPage_menu(name) {
            console.log(name)
            turnToPage(router, name)
          
            renderApi.handleSendPageName(activeName.value) //發送pageName到主進程,以此判斷窗口大小
          
          }

          主進程接收到指令后,根據參數,決定窗口設置為多大,代碼:

          ipcMain.on('send-page-name', (event, pageName)=> {
                      console.log('setWindowSize',pageName)
                      const loginSize={
                          width:500,
                          height:580
                      }
                      const pageSize={
                          width:1000,
                          height: 950
                      }
                      if (pageName && pageName=='normalLogin') {
                          win.setSize(loginSize.width, loginSize.height)
                          win.center()
                          win.setMenuBarVisibility(false)
          
                      } else {
                          if(this.judgePageSet(win,pageSize,loginSize)){
                              win.setSize(pageSize.width, pageSize.height)
                              win.center()
                              win.setMenuBarVisibility(true)
                          }
          
                      }
                  })

          至此不同頁面實現不同大小的窗口功能,就實現了。

          其實在我們項目中,還有另一種需求場景,那就是當通過注冊表把軟件注冊到系統右鍵后,上傳文件時,右下角有一個簡易窗口,窗口高度根據上傳文件數量來計算。這就需要判斷命令行參數、獲取文件下載地址等操作,更加復雜,但是應用場景應該不多,有興趣的同學可以私聊,此處不再贅述。

          四、右鍵菜單自定義

          我并沒有在實際項目中真正實現過右鍵菜單的自定義,但是道理和窗口是相通的,如果electron提供的右鍵菜單樣式無法滿足要求,那就舍棄框架提供的便捷菜單,通過進程間通信,手動實現。

          總結

          1. 舍棄系統窗口所有功能,利用通信機制,模擬實現需要的窗口功能。
          2. 判斷頁面發送的參數,為不同頁面的窗口設置不同的樣式。

          由于業務需求, 筆者要為公司開發幾款實用的瀏覽器插件,所以大致花了一天的時間,看完了谷歌瀏覽器插件開發文檔,在這里特地總結一下經驗, 并通過一個實際案例來復盤插件開發的流程和注意事項.

          你將收獲

          • 如何快速上手瀏覽器插件開發
          • 瀏覽器插件開發的核心概念
          • 瀏覽器插件的通信機制
          • 瀏覽器插件的數據存儲
          • 瀏覽器插件的應用場景
          • 開發一款抓取網站圖片資源的瀏覽器插件

          正文

          在開始正文之前,我們先來看看筆者總結的概覽:

          如果對瀏覽器插件開發比較熟悉的朋友可以直接看最后一節插件開發實戰。


          1.入門

          首先我們看看的瀏覽器插件的定義:

          瀏覽器插件是基于Web技術(例如HTML,JavaScript和CSS)構建的可以定制瀏覽體驗的小型軟件程序。它們使用戶可以根據個人需要或偏好來定制Chrome功能和行為。

          要想開發一款瀏覽器插件,我們只需要有一個manifest.json文件即可, 為了快速上手瀏覽器插件開發,我們需要把瀏覽器開發者工具打開, 具體步驟如下:

          1. 在谷歌瀏覽器中輸入chrome://extensions/
          2. 將開發者模式啟動

          1. 導入自己的瀏覽器插件包

          通過以上三個步驟我們就可以開啟瀏覽器插件開發之旅了.瀏覽器插件一般放在瀏覽器地址欄右側,我們可以在manifest.json文件配置插件的icon,并配置一定的規則,就能看到我們的瀏覽器插件圖標了,如下圖:

          下面我們來具體講解一下瀏覽器插件開發的核心概念.


          2.核心知識點

          瀏覽器插件一般涉及以下幾個核心文件:

          • manifest.json 用來配置所有和插件相關的配置(必須放在根目錄)
          • background.js 后臺腳本(后臺頁面),生命周期和瀏覽器一致,一般放置全局代碼
          • content-scripts 插件向頁面注入腳本的一種形式,我們可以通過content-scripts向頁面注入js和css資源,并可控制允許注入的范圍
          • popup 點擊插件圖標后打開的自定義窗口, 用來處理用戶交互

          筆者畫了一張簡圖來大致表示一下它們之間的關系:


          接下來我們來具體了解一下以上幾個核心知識點.

          2.1 manifest.json

          谷歌官網給我們提供了一份簡單的配置,如下:

          {
              "name": "My Extension",
              "version": "2.1",
              "description": "Gets information from Google.",
              "icons": {
                "128": "icon_16.png",
                "128": "icon_32.png",
                "128": "icon_48.png",
                "128": "icon_128.png"
              },
              "background": {
                "persistent": false,
                "scripts": ["background_script.js"]
              },
              "permissions": ["https://*.google.com/", "activeTab"],
              "browser_action": {
                "default_icon": "icon_16.png",
                "default_popup": "popup.html"
              }
           }
          復制代碼

          各字段含義介紹如下:

          • name 瀏覽器插件名稱, 將會在插件列表中顯示
          • description 瀏覽器插件簡介, 方便告訴開發者插件的功能和作用, 將會在插件列表中顯示
          • version 瀏覽器插件版本
          • icon 瀏覽器插件圖標

          • background 背景頁的腳本路徑,一般為插件目錄的相對地址
          • permissions 允許使用的瀏覽器API的權限,比如contextMenus(右鍵菜單), tabs(操作標簽), webRequest(使用web請求), storage(允許使用本地存儲), "http://*"(可以通過executeScript或者insertCSS訪問的網站)
          • browser_action 瀏覽器右上角圖標設置(包括popup頁面, 鼠標懸停時的標題, icon等)
          • content_scripts 需要直接注入頁面的javascript腳本
          • web_accessible_resources 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
          • chrome_url_overrides 覆蓋瀏覽器默認頁面(經常用來做瀏覽器的自定義桌面)
          • omnibox 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字(多用于自定義搜索攔截)
          • default_locale 默認語言(比如"zh_CN")

          文末會給出完整的配置文件地址,方便大家學習參考.

          2.2 background.js

          background頁面主要用來提供一些全局配置, 事件監聽, 業務轉發等.舉幾個常用案例:

          1. 定義右鍵菜單
          // background.js
          const systems={
            a: '趣談前端',
            b: '掘金',
            c: '微信'
          }
          
          chrome.runtime.onInstalled.addListener(function() {
            // 上下文菜單
            for (let key of Object.keys(systems)) {
              chrome.contextMenus.create({
                id: key,
                title: systems[key],
                type: 'normal',
                contexts: ['selection'],
              });
            }
          });
          
          // manifest.json
          {
              "permissions": ["contextMenus"]
          }
          復制代碼

          效果如下:


          1. 設置只有.com后綴的頁面才會激活插件
          chrome.runtime.onInstalled.addListener(function() {
            // 類似于什么時候激活瀏覽器插件圖標這種感覺
            chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
              chrome.declarativeContent.onPageChanged.addRules([{
                conditions: [new chrome.declarativeContent.PageStateMatcher({
                  pageUrl: {hostSuffix: '.com'},
                })
                ],
                actions: [new chrome.declarativeContent.ShowPageAction()]
              }]);
            });
          });
          復制代碼

          如下圖所示,當頁面地址的后綴不等于.com時,插件icon將不被激活:

          3. 和content_script或者popup頁面進行消息通信

          chrome.runtime.onMessage.addListener(
            function(request, sender, sendResponse) {
              console.log(sender.tab ?
                          "from a content script:" + sender.tab.url :
                          "from the extension");
              if (request.greeting=="hello")
                sendResponse({farewell: "goodbye"});
            });
          復制代碼

          2.3 content-scripts

          內容腳本一般植入會被植入到頁面中, 并且可以控制頁面中的dom. 我們可以利用它實現屏蔽網頁廣告, 定制頁面皮膚等操作. 在manifest.json中的基本配置如下:

          {
              "content_scripts": [{
              "matches": [
                  "http://*/*",
                  "https://*/*"
              ],
              "js": [
                  "lib/jquery3.4.min.js",
                  "content_script.js"
              ],
              "css": ["base.css"]
            }],
          }
          復制代碼

          以上代碼中我們定義了content_scripts允許注入的頁面范圍, 插入頁面的js以及css, 這樣我們就能輕松改變某一個頁面的樣式.比如我們可以在頁面中注入一個按鈕:

          在后面的瀏覽器插件案例中筆者會詳細介紹content_scripts的用法.


          2.4 popup

          popup是用戶點擊插件圖標時打開的一個小窗口,當失去焦點后窗口就立即關閉,我們一般用它來處理一些簡單的用戶交互和插件說明。

          由于popup窗口也是一個網頁,所以我們一般會建立一個popup.html和popup.js用來控制popup的頁面展示和交互.我們在manifest.json中配置如下:

          {
              "page_action": {
                  "default_title": "小夕圖片提取插件",
                  "default_popup": "popup.html"
            },
          }
          復制代碼

          這里要注意一點的是,我們在popup.html中不能直接使用script腳本,需要用引入腳本文件的方式.如下:

          <!DOCTYPE html>
          <html>
            <head>
              <title>在線圖片提取工具</title>
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            </head>
            <body>
              <div class="pop-wrap">
              </div>
              <script src="lib/jquery3.4.min.js"></script>
              <script src="popup.js"></script>
            </body>
          </html>
          復制代碼

          以下是筆者寫的一個插件的popup頁面:


          3.通信機制

          對于一個相對復雜的瀏覽器插件來說,我們不僅僅只操作dom或者提供基本的功能就行了,我們還需要向第三方或者自己的服務器抓取有用的頁面數據,這個時候就需要用到插件的通信機制了.

          因為content_script腳本存在于當前頁面,受同源策略影響,導致我們無法將捕獲到的數據傳給第三方平臺或者自己的服務器, 所以我們需要基于瀏覽器的通信API.以下是谷歌瀏覽器插件的通信流程:


          3.1 popup和background相互通信

          由官方文檔可知popup可以直接訪問background頁的方法,所以popup可以直接與其通信:

          // background.js
          var getData=(data)=> { console.log('拿到數據:' + data) }
          // popup.js
          let bgObj=chrome.extension.getBackgroundPage();
          bgObj.getData(); // 訪問bg的函數
          復制代碼

          3.2 popup或者background頁和content_script通信

          這里我們使用chrome的tabs API,如下:

          // popup.js
          // 發送消息給content_script
          chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
              chrome.tabs.sendMessage(tabs[0].id, "activeBtn", function(response) {
                console.log(response);
              });
            });
            
          // 接收消息
          chrome.runtime.onMessage.addListener(
            function(request, sender, sendResponse) {
              console.log(sender.tab ?
                          "from a content script:" + sender.tab.url :
                          "from the extension");
              if (request.greeting=="hello")
                sendResponse({farewell: "goodbye"});
            });
          復制代碼

          content_script接收和發送消息:

          // 接收消息
          chrome.runtime.onMessage.addListener(
            function(message, sender, sendResponse) {
              if (message=="activeBtn"){
                // ...
                sendResponse({farewell: "激活成功"});
              }
           });
           
           // 主動發送消息
           chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
             console.log(response, document.body);
             // document.body.style.backgroundColor="orange"
          });
          復制代碼

          有關消息的長連接,在谷歌官網也寫的很清楚:

          我們可以采用如下方式進行長連接:

          // content_script.js
          var port=chrome.runtime.connect({name: "徐小夕"});
          port.postMessage({Ling: "你好"});
          port.onMessage.addListener(function(msg) {
            if (msg.question=="你是做什么滴?")
              port.postMessage({answer: "搬磚"});
            else if (msg.question=="搬磚有錢嗎?")
              port.postMessage({answer: "木有"});
          });
          
          // popup.js
          chrome.runtime.onConnect.addListener(function(port) {
            port.onMessage.addListener(function(msg) {
              if (msg.Ling=="你好")
                port.postMessage({question: "你是做什么滴?"});
              else if (msg.answer=="搬磚")
                port.postMessage({question: "搬磚有錢嗎?"});
              else if (msg.answer=="木有")
                port.postMessage({question: "太難了."});
            });
          });
          復制代碼

          4.數據存儲

          chrome.storage用來針對插件全局進行數據存儲,我們在任何一個頁面(popup或content_script或background)下存儲了數據,我們在以上三個頁面都可以獲取到, 具體用法如下:

          獲取數據
          chrome.storage.sync.get('imgArr', function(data) {
            console.log(data)
          });
          // 保存數據
          chrome.storage.sync.set({'imgArr': imgArr}, function() {
              console.log('保存成功');
            });
            
          // 另一種方式
          chrome.storage.local.set({key: value}, function() {
            console.log('Value is set to ' + value);
          });
          復制代碼

          5.應用場景

          谷歌瀏覽器的插件應用場景很多,正如文章開頭的思維導圖中寫的.以下是筆者總結的一些應用場景,大家感興趣可以嘗試去實現:

          • 谷歌瀏覽器自定義桌面
          • 網頁性能分析工具
          • 網頁爬蟲
          • 埋點工具
          • 網頁熱力圖生成工具
          • 安全攔截插件
          • 廣告過濾插件
          • 網站動態換膚
          • 第三方數據導入
          • 代碼格式化工具
          • 在線協作工具
          • 防作弊插件

          還有很多實用工具可以開發,大家可以好好把玩。接下來就來通過實現一個網頁圖片提取插件,來總結以下瀏覽器插件開發流程。

          6.開發一款抓取網站圖片資源的瀏覽器插件

          首先還是按照筆者的風格,在開發任何一種工具之前都要明確需求,所以我們來看看該插件的功能點:

          • 能植入網頁按鈕,通過點擊按鈕捕獲網頁圖片
          • 能在用戶端展示捕獲的圖片
          • 點擊插件能預覽捕獲的圖片

          基本上就這幾個功能,接下來我會展示核心代碼,在介紹代碼之前我們先預覽一下插件的實現效果:

          插件目錄結構如下:

          因為插件的開發比較簡單,所以我直接用jquery開發。這里我們主要關注popup.js和content_script.js, popup.js中主要用來獲取從content_script頁傳過來的圖片數據,并展示在popup.html中,另外又一個需要注意的是當頁面沒有注入生成按鈕時,popupu需要發送信息給content頁面,主動讓其生成按鈕,代碼如下:

          chrome.storage.sync.get('imgArr', function(data) {
            data.imgArr && data.imgArr.forEach(item=> {
              var imgWrap=$("<div class='img-box'></div>")
              var img=$("<img src='" + item + "' alt='" + item + "' />")
              imgWrap.append(img);
              $('#content').append(imgWrap);
              $('.empty').hide();
            })
          });
          
          $('#activeBtn').click(function(element) {
            chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
              chrome.tabs.sendMessage(tabs[0].id, "activeBtn", function(response) {
                console.log(response);
              });
            });
          });
          復制代碼

          對于content頁面,我們需要實現的是動態生成按鈕,并且在頁面中植入彈窗來展示獲取到的圖片,另一方面需要將圖片數據傳遞給storage,以便popup頁面可以獲取圖片數據。

          由于頁面比較簡單,筆者就不用過多的第三方庫了,筆者先簡單手寫一個modal組件,代碼如下:

          // 彈窗
          ~function Modal() {
            var modal;
            
            if(this instanceof Modal) {
              this.init=function(opt) {
                modal=$("<div class='modal'></div>");
                var title=$("<div class='modal-title'>" + opt.title + "</div>");
                var close_btn=$("<span class='modal-close-btn'>X</span>");
                var content=$("<div class='modal-content'></div>");
                var mask=$("<div class='modal-mask'></div>");
                close_btn.click(function(){
                  modal.hide()
                })
                title.append(close_btn);
                content.append(title);
                content.append(opt.content);
                modal.append(content);
                modal.append(mask);
                $('body').append(modal);
              }
              this.show=function(opt) {
                if(modal) {
                  modal.show();
                }else {
                  var options={
                    title: opt.title || '標題',
                    content: opt.content || ''
                  }
                  this.init(options)
                  modal.show();
                }
              }
              this.hide=function() {
                modal.hide();
              }
            }else {
              window.Modal=new Modal()
            }
          }()
          復制代碼

          第一步,我們先批量獲取頁面圖片數據:

          var imgArr=[]
            $('img').each(function(i) {
              var src=$(this).attr('src');
              var realSrc=/^(http|https)/.test(src) ? src : location.protocol+ '//' + location.host + src;
              imgArr.push(realSrc)
            })
          復制代碼

          因為圖片的src路徑可能是相對地址,所以筆者在這里用正則簡單處理以下,當然我們可以進行更細粒度的控制。

          第二步,將圖片數據存儲到storage中:

          chrome.storage.sync.set({'imgArr': imgArr}, function() {
              console.log('保存成功');
            });
          復制代碼

          第三步,生成預覽圖片的彈窗,這里用筆者上面實現的modal組件:

          Modal.show({
            title: '提取結果',
            content: imgBox
          })
          復制代碼

          第四步,當popup發送激活按鈕的通知時,我們要在網頁中動態插入生成按鈕:

          chrome.runtime.onMessage.addListener(
            function(message, sender, sendResponse) {
              if (message=="activeBtn"){
                if(!$('.crawl-btn')) {
                  $('body').append("<div class='crawl-btn'>提取</div>")
                }else {
                  $('.crawl-btn').css("background-color","orange");
                  setTimeout(()=> {
                    $('.crawl-btn').css("background-color","#06c");
                  }, 3000);
                }
                sendResponse({farewell: "激活成功"});
              }
           });
          復制代碼

          setTimeout那段純屬是為了吸引用戶視線,當然我們可以用更優雅的方式來處理。 插件核心代碼主要是這些,當然還有很多細節要考慮,我把配置文件和一些細節放到github了,如果感興趣的朋友可以安裝感受一下。

          github地址:一款提取網頁圖片數據的瀏覽器插件

          最后

          如果想學習更多H5游戲, webpacknodegulpcss3javascriptnodeJScanvas數據可視化等前端知識和實戰,歡迎在《趣談前端》學習討論,共同探索前端的邊界。


          主站蜘蛛池模板: 久久久久人妻精品一区三寸| 日本一区频道在线视频| 中文字幕av日韩精品一区二区 | 精品国产免费一区二区| 欧美日韩一区二区成人午夜电影| 日韩精品在线一区二区| 国产一区二区三区美女| 2022年亚洲午夜一区二区福利| 久久久91精品国产一区二区三区| 日本一区二区三区在线视频 | 秋霞电影网一区二区三区| 精品国产一区在线观看| 精品国产一区二区三区av片| 日韩精品午夜视频一区二区三区| 国产精品无码一区二区在线观| 欧美人妻一区黄a片| 国产精品盗摄一区二区在线| 国产伦精品一区二区三区在线观看 | 亚洲熟女综合色一区二区三区| 日本高清一区二区三区| 亚洲AV网一区二区三区| 国产在线精品观看一区| 国产高清在线精品一区小说| 中文字幕av无码一区二区三区电影| 亚洲日韩精品一区二区三区无码 | 中文字幕在线观看一区| 中文字幕一区二区人妻性色 | 日韩精品无码Av一区二区| 一区免费在线观看| 任你躁国产自任一区二区三区| 国99精品无码一区二区三区 | 亚洲av色香蕉一区二区三区蜜桃| 手机福利视频一区二区| 亚洲国产精品一区二区三区久久 | 亚洲AV无码一区二区乱子仑| 国产综合视频在线观看一区| 国产情侣一区二区三区| 91在线视频一区| 国产女人乱人伦精品一区二区| 亚洲一区二区三区影院| 无码一区18禁3D|