整合營銷服務商

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

          免費咨詢熱線:

          「干貨」Chrome插件(擴展)開發全攻略,看這篇就

          「干貨」Chrome插件(擴展)開發全攻略,看這篇就夠了!

          、寫在前面

          本文圖片較多,且圖片服務器帶寬有限,請耐心等待加載完畢。

          等待時間可以了解下Freemen 平臺

          Freemen App是一款專注于IT程序員求職招聘的一個求職平臺,旨在幫助IT技術工作者能更好更快入職及努力協調IT技術者工作和生活的關系,讓工作更自由!


          本文目錄:

          二、前言

          2.1. 什么是Chrome插件

          嚴格來講,我們正在說的東西應該叫Chrome擴展(Chrome Extension),真正意義上的Chrome插件是更底層的瀏覽器功能擴展,可能需要對瀏覽器源碼有一定掌握才有能力去開發。鑒于Chrome插件的叫法已經習慣,本文也全部采用這種叫法,但讀者需深知本文所描述的Chrome插件實際上指的是Chrome擴展。

          Chrome插件是一個用Web技術開發、用來增強瀏覽器功能的軟件,它其實就是一個由HTML、CSS、JS、圖片等資源組成的一個.crx后綴的壓縮包.

          個人猜測crx可能是Chrome Extension如下3個字母的簡寫:

          另外,其實不只是前端技術,Chrome插件還可以配合C++編寫的dll動態鏈接庫實現一些更底層的功能(NPAPI),比如全屏幕截圖。

          由于安全原因,Chrome瀏覽器42以上版本已經陸續不再支持NPAPI插件,取而代之的是更安全的PPAPI。


          2.2. 學習Chrome插件開發有什么意義

          增強瀏覽器功能,輕松實現屬于自己的“定制版”瀏覽器,等等。

          Chrome插件提供了很多實用API供我們使用,包括但不限于:

          • 書簽控制;
          • 下載控制;
          • 窗口控制;
          • 標簽控制;
          • 網絡請求控制,各類事件監聽;
          • 自定義原生菜單;
          • 完善的通信機制;
          • 等等;

          2.3. 為什么是Chrome插件而不是Firefox插件

          1

          Chrome占有率更高,更多人用;

          2

          開發更簡單

          3

          應用場景更廣泛,Firefox插件只能運行在Firefox上,而Chrome除了Chrome瀏覽器之外,還可以運行在所有webkit內核的國產瀏覽器,比如360極速瀏覽器、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等;

          4

          除此之外,Firefox瀏覽器也對Chrome插件的運行提供了一定的支持;

          三、開發與調試

          Chrome插件沒有嚴格的項目結構要求,只要保證本目錄有一個manifest.json即可,也不需要專門的IDE,普通的web開發工具即可。

          從右上角菜單->更多工具->擴展程序可以進入 插件管理頁面,也可以直接在地址欄輸入 chrome://extensions 訪問。

          勾選開發者模式即可以文件夾的形式直接加載插件,否則只能安裝.crx格式的文件。Chrome要求插件必須從它的Chrome應用商店安裝,其它任何網站下載的都無法直接安裝,所以,其實我們可以把crx文件解壓,然后通過開發者模式直接加載。

          開發中,代碼有任何改動都必須重新加載插件,只需要在插件管理頁按下Ctrl+R即可,以防萬一最好還把頁面刷新一下。

          四、核心介紹

          4.1. manifest.json

          這是一個Chrome插件最重要也是必不可少的文件,用來配置所有和插件相關的配置,必須放在根目錄。其中,manifest_version、name、version3個是必不可少的,description和icons是推薦的。

          下面給出的是一些常見的配置項,均有中文注釋,完整的配置文檔請戳這里。

          {
            // 清單文件的版本,這個必須寫,而且必須是2
            "manifest_version": 2,
            // 插件的名稱
            "name": "demo",
            // 插件的版本
            "version": "1.0.0",
            // 插件描述
            "description": "簡單的Chrome擴展demo",
            // 圖標,一般偷懶全部用一個尺寸的也沒問題
            "icons":
            {
              "16": "img/icon.png",
              "48": "img/icon.png",
              "128": "img/icon.png"
            },
            // 會一直常駐的后臺JS或后臺頁面
            "background":
            {
              // 2種指定方式,如果指定JS,那么會自動生成一個背景頁
              "page": "background.html"
              //"scripts": ["js/background.js"]
            },
            // 瀏覽器右上角圖標設置,browser_action、page_action、app必須三選一
            "browser_action": 
            {
              "default_icon": "img/icon.png",
              // 圖標懸停時的標題,可選
              "default_title": "這是一個示例Chrome插件",
              "default_popup": "popup.html"
            },
            // 當某些特定頁面打開才顯示的圖標
            /*"page_action":
            {
              "default_icon": "img/icon.png",
              "default_title": "我是pageAction",
              "default_popup": "popup.html"
            },*/
            // 需要直接注入頁面的JS
            "content_scripts": 
            [
              {
                //"matches": ["http://*/*", "https://*/*"],
                // "<all_urls>" 表示匹配所有地址
                "matches": ["<all_urls>"],
                // 多個JS按順序注入
                "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
                // JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全局樣式
                "css": ["css/custom.css"],
                // 代碼注入的時間,可選值:"document_start", "document_end", or "document_idle",最后一個表示頁面空閑時,默認document_idle
                "run_at": "document_start"
              },
              // 這里僅僅是為了演示content-script可以配置多個規則
              {
                "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
                "js": ["js/show-image-content-size.js"]
              }
            ],
            // 權限申請
            "permissions":
            [
              "contextMenus", // 右鍵菜單
              "tabs", // 標簽
              "notifications", // 通知
              "webRequest", // web請求
              "webRequestBlocking",
              "storage", // 插件本地存儲
              "http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
              "https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
            ],
            // 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
            "web_accessible_resources": ["js/inject.js"],
            // 插件主頁,這個很重要,不要浪費了這個免費廣告位
            "homepage_url": "https://www.baidu.com",
            // 覆蓋瀏覽器默認頁面
            "chrome_url_overrides":
            {
              // 覆蓋瀏覽器默認的新標簽頁
              "newtab": "newtab.html"
            },
            // Chrome40以前的插件配置頁寫法
            "options_page": "options.html",
            // Chrome40以后的插件配置頁寫法,如果2個都寫,新版Chrome只認后面這一個
            "options_ui":
            {
              "page": "options.html",
              // 添加一些默認的樣式,推薦使用
              "chrome_style": true
            },
            // 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字
            "omnibox": { "keyword" : "go" },
            // 默認語言
            "default_locale": "zh_CN",
            // devtools頁面入口,注意只能指向一個HTML文件,不能是JS文件
            "devtools_page": "devtools.html"
          }


          4.2. content-scripts

          所謂content-scripts,其實就是Chrome插件中向頁面注入腳本的一種形式(雖然名為script,其實還可以包括css的),借助content-scripts我們可以實現通過配置的方式輕松向指定頁面注入JS和CSS(如果需要動態注入,可以參考下文),最常見的比如:廣告屏蔽、頁面CSS定制,等等。

          示例配置:

          {
            // 需要直接注入頁面的JS
            "content_scripts": 
            [
              {
                //"matches": ["http://*/*", "https://*/*"],
                // "<all_urls>" 表示匹配所有地址
                "matches": ["<all_urls>"],
                // 多個JS按順序注入
                "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
                // JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全局樣式
                "css": ["css/custom.css"],
                // 代碼注入的時間,可選值:"document_start", "document_end", or "document_idle",最后一個表示頁面空閑時,默認document_idle
                "run_at": "document_start"
              }
            ],
          }

          特別注意,如果沒有主動指定run_at為document_start(默認為document_idle),下面這種代碼是不會生效的:

          document.addEventListener('DOMContentLoaded', function()
          {
            console.log('我被執行了!');
          });

          content-scripts和原始頁面共享DOM,但是不共享JS,如要訪問頁面JS(例如某個JS變量),只能通過injected js來實現。content-scripts不能訪問絕大部分chrome.xxx.api,除了下面這4種:

          • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
          • chrome.i18n
          • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
          • chrome.storage


          其實看到這里不要悲觀,這些API絕大部分時候都夠用了,非要調用其它API的話,你還可以通過通信來實現讓background來幫你調用(關于通信,后文有詳細介紹)。

          好了,Chrome插件給我們提供了這么強大的JS注入功能,剩下的就是發揮你的想象力去玩弄瀏覽器了。


          4.3. background

          后臺(姑且這么翻譯吧),是一個常駐的頁面,它的生命周期是插件中所有類型頁面中最長的,它隨著瀏覽器的打開而打開,隨著瀏覽器的關閉而關閉,所以通常把需要一直運行的、啟動就運行的、全局的代碼放在background里面。

          background的權限非常高,幾乎可以調用所有的Chrome擴展API(除了devtools),而且它可以無限制跨域,也就是可以跨域訪問任何網站而無需要求對方設置CORS。

          經過測試,其實不止是background,所有的直接通過chrome-extension://id/xx.html這種方式打開的網頁都可以無限制跨域。

          配置中,background可以通過page指定一張網頁,也可以通過scripts直接指定一個JS,Chrome會自動為這個JS生成一個默認的網頁:

          {
            // 會一直常駐的后臺JS或后臺頁面
            "background":
            {
              // 2種指定方式,如果指定JS,那么會自動生成一個背景頁
              "page": "background.html"
              //"scripts": ["js/background.js"]
            },
          }
          需要特別說明的是,雖然你可以通過chrome-extension://xxx/background.html
          直接打開后臺頁,但是你打開的后臺頁和真正一直在后臺運行的那個頁面不是同一個,換句話說,你可以打開無數個background.html,但是真正在后臺常駐的只有一個,而且這個你永遠看不到它的界面,只能調試它的代碼。


          4.4. event-pages

          這里順帶介紹一下event-pages,它是一個什么東西呢?鑒于background生命周期太長,長時間掛載后臺可能會影響性能,所以Google又弄一個event-pages,在配置文件上,它與background的唯一區別就是多了一個persistent參數:

          {
            "background":
            {
              "scripts": ["event-page.js"],
              "persistent": false
            },
          }

          它的生命周期是:在被需要時加載,在空閑時被關閉,什么叫被需要時呢?比如第一次安裝、插件更新、有content-script向它發送消息,等等。

          除了配置文件的變化,代碼上也有一些細微變化,個人這個簡單了解一下就行了,一般情況下background也不會很消耗性能的。


          4.5. popup

          popup是點擊browser_action或者page_action圖標時打開的一個小窗口網頁,焦點離開網頁就立即關閉,一般用來做一些臨時性的交互。

          popup可以包含任意你想要的HTML內容,并且會自適應大小。可以通過default_popup字段來指定popup頁面,也可以調用setPopup()方法。

          配置方式:

          {
            "browser_action":
            {
              "default_icon": "img/icon.png",
              // 圖標懸停時的標題,可選
              "default_title": "這是一個示例Chrome插件",
              "default_popup": "popup.html"
            }
          }

          需要特別注意的是,由于單擊圖標打開popup,焦點離開又立即關閉,所以popup頁面的生命周期一般很短,需要長時間運行的代碼千萬不要寫在popup里面。

          在權限上,它和background非常類似,它們之間最大的不同是生命周期的不同,popup中可以直接通過chrome.extension.getBackgroundPage()獲取background的window對象。


          4.6. injected-script

          這里的injected-script是我給它取的,指的是通過DOM操作的方式向頁面注入的一種JS。為什么要把這種JS單獨拿出來討論呢?又或者說為什么需要通過這種方式注入JS呢?

          這是因為content-script有一個很大的“缺陷”,也就是無法訪問頁面中的JS,雖然它可以操作DOM,但是DOM卻不能調用它,也就是無法在DOM中通過綁定事件的方式調用content-script中的代碼(包括直接寫onclick和addEventListener2種方式都不行),但是,“在頁面上添加一個按鈕并調用插件的擴展API”是一個很常見的需求,那該怎么辦呢?其實這就是本小節要講的。

          在content-script中通過DOM方式向頁面注入inject-script代碼示例:

          // 向頁面注入JS
          function injectCustomJs(jsPath)
          {
            jsPath=jsPath || 'js/inject.js';
            var temp=document.createElement('script');
            temp.setAttribute('type', 'text/javascript');
            // 獲得的地址類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
            temp.src=chrome.extension.getURL(jsPath);
            temp.onload=function()
          {
              // 放在頁面不好看,執行完后移除掉
              this.parentNode.removeChild(this);
            };
            document.head.appendChild(temp);
          }
          

          你以為這樣就行了?執行一下你會看到如下報錯:

          Denying load of chrome-extension://efbllncjkjiijkppagepehoekjojdclc/js/inject.js. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.

          意思就是你想要在web中直接訪問插件中的資源的話必須顯示聲明才行,配置文件中增加如下:

          {
            // 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
            "web_accessible_resources": ["js/inject.js"],
          }

          至于inject-script 如何調用content-script中的代碼,后面我會在專門的一個消息通信章節詳細介紹。


          4.7. homepage_url

          開發者或者插件主頁設置,一般會在如下2個地方顯示:

          五、Chrome插件的8種展示形式

          5.1. browserAction(瀏覽器右上角)

          通過配置browser_action可以在瀏覽器的右上角增加一個圖標,一個browser_action可以擁有一個圖標,一個tooltip,一個badge和一個popup。

          示例配置如下:

          "browser_action":
          {
            "default_icon": "img/icon.png",
            "default_title": "這是一個示例Chrome插件",
            "default_popup": "popup.html"
          }

          5.1.1. 圖標

          browser_action圖標推薦使用寬高都為19像素的圖片,更大的圖標會被縮小,格式隨意,一般推薦png,可以通過manifest中default_icon字段配置,也可以調用setIcon()方法。

          5.1.2. tooltip

          修改browser_action的manifest中default_title字段,或者調用setTitle()方法。

          5.1.3. badge

          所謂badge就是在圖標上顯示一些文本,可以用來更新一些小的擴展狀態提示信息。因為badge空間有限,所以只支持4個以下的字符(英文4個,中文2個)。badge無法通過配置文件來指定,必須通過代碼實現,設置badge文字和顏色可以分別使用setBadgeText()和setBadgeBackgroundColor()。

          chrome.browserAction.setBadgeText({text: 'new'});
          chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 255]});

          效果:


          5.2. pageAction(地址欄右側)

          所謂pageAction,指的是只有當某些特定頁面打開才顯示的圖標,它和browserAction最大的區別是一個始終都顯示,一個只在特定情況才顯示。

          需要特別說明的是早些版本的Chrome是將pageAction放在地址欄的最右邊,左鍵單擊彈出popup,右鍵單擊則彈出相關默認的選項菜單:

          而新版的Chrome更改了這一策略,pageAction和普通的browserAction一樣也是放在瀏覽器右上角,只不過沒有點亮時是灰色的,點亮了才是彩色的,灰色時無論左鍵還是右鍵單擊都是彈出選項:

          具體是從哪一版本開始改的沒去仔細考究,反正知道v50.0的時候還是前者,v58.0的時候已改為后者。

          調整之后的pageAction我們可以簡單地把它看成是可以置灰的browserAction。

          chrome.pageAction.show(tabId) 顯示圖標;
          chrome.pageAction.hide(tabId) 隱藏圖標;

          示例(只有打開百度才顯示圖標)

          // manifest.json
          {
            "page_action":
            {
              "default_icon": "img/icon.png",
              "default_title": "我是pageAction",
              "default_popup": "popup.html"
            },
            "permissions": ["declarativeContent"]
          }
          
          
          // background.js
          chrome.runtime.onInstalled.addListener(function(){
            chrome.declarativeContent.onPageChanged.removeRules(undefined, function(){
              chrome.declarativeContent.onPageChanged.addRules([
                {
                  conditions: [
                    // 只有打開百度才顯示pageAction
                    new chrome.declarativeContent.PageStateMatcher({pageUrl: {urlContains: 'baidu.com'}})
                  ],
                  actions: [new chrome.declarativeContent.ShowPageAction()]
                }
              ]);
            });
          });

          效果圖:


          5.3. 右鍵菜單

          通過開發Chrome插件可以自定義瀏覽器的右鍵菜單,主要是通過chrome.contextMenusAPI實現,右鍵菜單可以出現在不同的上下文,比如普通頁面、選中的文字、圖片、鏈接,等等,如果有同一個插件里面定義了多個菜單,Chrome會自動組合放到以插件名字命名的二級菜單里,如下:

          5.3.1. 最簡單的右鍵菜單示例

          // manifest.json
          {"permissions": ["contextMenus"]}
          
          
          // background.js
          chrome.contextMenus.create({
            title: "測試右鍵菜單",
            onclick: function(){alert('您點擊了右鍵菜單!');}
          });

          效果:

          5.3.2. 添加右鍵百度搜索

          // manifest.json
          {"permissions": ["contextMenus", "tabs"]}
          
          
          // background.js
          chrome.contextMenus.create({
            title: '使用度娘搜索:%s', // %s表示選中的文字
            contexts: ['selection'], // 只有當選中文字時才會出現此右鍵菜單
            onclick: function(params)
          {
              // 注意不能使用location.href,因為location是屬于background的window對象
              chrome.tabs.create({url: 'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(params.selectionText)});
            }
          });

          效果如下:


          5.3.3. 語法說明

          這里只是簡單列舉一些常用的,完整API參見:https://developer.chrome.com/extensions/contextMenus

          chrome.contextMenus.create({
            type: 'normal', // 類型,可選:["normal", "checkbox", "radio", "separator"],默認 normal
            title: '菜單的名字', // 顯示的文字,除非為“separator”類型否則此參數必需,如果類型為“selection”,可以使用%s顯示選定的文本
            contexts: ['page'], // 上下文環境,可選:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],默認page
            onclick: function(){}, // 單擊時觸發的方法
            parentId: 1, // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成為父菜單項的子菜單
            documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些頁面顯示此右鍵菜單
          });
          // 刪除某一個菜單項
          chrome.contextMenus.remove(menuItemId);
          // 刪除所有自定義右鍵菜單
          chrome.contextMenus.removeAll();
          // 更新某一個菜單項
          chrome.contextMenus.update(menuItemId, updateProperties);
          

          5.4. override(覆蓋特定頁面)

          使用override頁可以將Chrome默認的一些特定頁面替換掉,改為使用擴展提供的頁面。

          擴展可以替代如下頁面:

          • 歷史記錄:從工具菜單上點擊歷史記錄時訪問的頁面,或者從地址欄直接輸入 chrome://history
          • 新標簽頁:當創建新標簽的時候訪問的頁面,或者從地址欄直接輸入 chrome://newtab
          • 書簽:瀏覽器的書簽,或者直接輸入 chrome://bookmarks

          注意:

          • 一個擴展只能替代一個頁面;
          • 不能替代隱身窗口的新標簽頁;
          • 網頁必須設置title,否則用戶可能會看到網頁的URL,造成困擾;


          下面的截圖是默認的新標簽頁和被擴展替換掉的新標簽頁。

          代碼(注意,一個插件只能替代一個默認頁,以下僅為演示):

          "chrome_url_overrides":
          {
            "newtab": "newtab.html",
            "history": "history.html",
            "bookmarks": "bookmarks.html"
          }
          

          5.5. devtools(開發者工具)

          5.5.1. 預熱

          使用過vue的應該見過這種類型的插件:

          是的,Chrome允許插件在開發者工具(devtools)上動手腳,主要表現在:

          自定義一個和多個和Elements、Console、Sources等同級別的面板;

          自定義側邊欄(sidebar),目前只能自定義Elements面板的側邊欄;

          先來看2張簡單的demo截圖,自定義面板(判斷當前頁面是否使用了jQuery):

          自定義側邊欄(獲取當前頁面所有圖片):

          5.5.2. devtools擴展介紹

          來一張官方圖片:

          每打開一個開發者工具窗口,都會創建devtools頁面的實例,F12窗口關閉,頁面也隨著關閉,所以devtools頁面的生命周期和devtools窗口是一致的。devtools頁面可以訪問一組特有的DevTools API以及有限的擴展API,這組特有的DevTools API只有devtools頁面才可以訪問,background都無權訪問,這些API包括:

          chrome.devtools.panels:面板相關;
          chrome.devtools.inspectedWindow:獲取被審查窗口的有關信息;
          chrome.devtools.network:獲取有關網絡請求的信息;

          大部分擴展API都無法直接被DevTools頁面調用,但它可以像content-script一樣直接調用chrome.extension和chrome.runtimeAPI,同時它也可以像content-script一樣使用Message交互的方式與background頁面進行通信。


          5.5.3. 實例:創建一個devtools擴展

          首先,要針對開發者工具開發插件,需要在清單文件聲明如下:

          {
            // 只能指向一個HTML文件,不能是JS文件
            "devtools_page": "devtools.html"
          }

          這個devtools.html里面一般什么都沒有,就引入一個js:

          <!DOCTYPE html>
          <html>
          <head></head>
          <body>
            <script type="text/javascript" src="js/devtools.js"></script>
          </body>
          </html>

          可以看出來,其實真正代碼是devtools.js,html文件是“多余”的,所以這里覺得有點坑,devtools_page干嘛不允許直接指定JS呢?

          再來看devtools.js的代碼:

          // 創建自定義面板,同一個插件可以創建多個自定義面板
          // 幾個參數依次為:panel標題、圖標(其實設置了也沒地方顯示)、要加載的頁面、加載成功后的回調
          chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel)
          {
            console.log('自定義面板創建成功!'); // 注意這個log一般看不到
          });
          
          
          // 創建自定義側邊欄
          chrome.devtools.panels.elements.createSidebarPane("Images", function(sidebar)
          {
            // sidebar.setPage('../sidebar.html'); // 指定加載某個頁面
            sidebar.setExpression('document.querySelectorAll("img")', 'All Images'); // 通過表達式來指定
            //sidebar.setObject({aaa: 111, bbb: 'Hello World!'}); // 直接設置顯示某個對象
          });

          setPage時的效果:

          以下截圖示例的代碼:

          // 檢測jQuery
          document.getElementById('check_jquery').addEventListener('click', function()
          {
            // 訪問被檢查的頁面DOM需要使用inspectedWindow
            // 簡單例子:檢測被檢查頁面是否使用了jQuery
            chrome.devtools.inspectedWindow.eval("jQuery.fn.jquery", function(result, isException)
          {
              var html='';
              if (isException) html='當前頁面沒有使用jQuery。';
              else html='當前頁面使用了jQuery,版本為:'+result;
              alert(html);
            });
          });
          
          
          // 打開某個資源
          document.getElementById('open_resource').addEventListener('click', function()
          {
            chrome.devtools.inspectedWindow.eval("window.location.href", function(result, isException)
          {
              chrome.devtools.panels.openResource(result, 20, function()
          {
                console.log('資源打開成功!');
              });
            });
          });
          
          
          // 審查元素
          document.getElementById('test_inspect').addEventListener('click', function()
          {
            chrome.devtools.inspectedWindow.eval("inspect(document.images[0])", function(result, isException){});
          });
          
          
          // 獲取所有資源
          document.getElementById('get_all_resources').addEventListener('click', function()
          {
            chrome.devtools.inspectedWindow.getResources(function(resources)
          {
              alert(JSON.stringify(resources));
            });
          });
          

          5.5.4. 調試技巧

          修改了devtools頁面的代碼時,需要先在 chrome://extensions 頁面按下Ctrl+R重新加載插件,然后關閉再打開開發者工具即可,無需刷新頁面(而且只刷新頁面不刷新開發者工具的話是不會生效的)。

          由于devtools本身就是開發者工具頁面,所以幾乎沒有方法可以直接調試它,直接用 chrome-extension://extid/devtools.html"的方式打開頁面肯定報錯,因為不支持相關特殊API,只能先自己寫一些方法屏蔽這些錯誤,調試通了再放開。

          5.6. option(選項頁)

          所謂options頁,就是插件的設置頁面,有2個入口,一個是右鍵圖標有一個“選項”菜單,還有一個在插件管理頁面:

          在Chrome40以前,options頁面和其它普通頁面沒什么區別,Chrome40以后則有了一些變化。

          我們先看老版的options:

          {
            // Chrome40以前的插件配置頁寫法
            "options_page": "options.html",
          }

          這個頁面里面的內容就隨你自己發揮了,配置之后在插件管理頁就會看到一個選項按鈕入口,點進去就是打開一個網頁,沒啥好講的。

          效果:

          再來看新版的optionsV2:

          {
            "options_ui":
            {
                "page": "options.html",
              // 添加一些默認的樣式,推薦使用
                "chrome_style": true
            },
          }

          options.html的代碼我們沒有任何改動,只是配置文件改了,之后效果如下:

          看起來是不是高大上了?

          幾點注意:

          • 為了兼容,建議2種都寫,如果都寫了,Chrome40以后會默認讀取新版的方式;
          • 新版options中不能使用alert;
          • 數據存儲建議用chrome.storage,因為會隨用戶自動同步;


          5.7. omnibox

          omnibox是向用戶提供搜索建議的一種方式。先來看個gif圖以便了解一下這東西到底是個什么鬼:

          注冊某個關鍵字以觸發插件自己的搜索建議界面,然后可以任意發揮了。

          首先,配置文件如下:

          {
            // 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字
            "omnibox": { "keyword" : "go" },
          }

          然后background.js中注冊監聽事件:

          // omnibox 演示
          chrome.omnibox.onInputChanged.addListener((text, suggest)=> {
            console.log('inputChanged: ' + text);
            if(!text) return;
            if(text=='美女') {
              suggest([
                {content: '中國' + text, description: '你要找“中國美女”嗎?'},
                {content: '日本' + text, description: '你要找“日本美女”嗎?'},
                {content: '泰國' + text, description: '你要找“泰國美女或人妖”嗎?'},
                {content: '韓國' + text, description: '你要找“韓國美女”嗎?'}
              ]);
            }
            else if(text=='微博') {
              suggest([
                {content: '新浪' + text, description: '新浪' + text},
                {content: '騰訊' + text, description: '騰訊' + text},
                {content: '搜狐' + text, description: '搜索' + text},
              ]);
            }
            else {
              suggest([
                {content: '百度搜索 ' + text, description: '百度搜索 ' + text},
                {content: '谷歌搜索 ' + text, description: '谷歌搜索 ' + text},
              ]);
            }
          });
          
          
          // 當用戶接收關鍵字建議時觸發
          chrome.omnibox.onInputEntered.addListener((text)=> {
              console.log('inputEntered: ' + text);
            if(!text) return;
            var href='';
              if(text.endsWith('美女')) href='http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word=' + text;
            else if(text.startsWith('百度搜索')) href='https://www.baidu.com/s?ie=UTF-8&wd=' + text.replace('百度搜索 ', '');
            else if(text.startsWith('谷歌搜索')) href='https://www.google.com.tw/search?q=' + text.replace('谷歌搜索 ', '');
            else href='https://www.baidu.com/s?ie=UTF-8&wd=' + text;
            openUrlCurrentTab(href);
          });
          // 獲取當前選項卡ID
          function getCurrentTabId(callback)
          {
            chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
          {
              if(callback) callback(tabs.length ? tabs[0].id: null);
            });
          }
          
          
          // 當前標簽打開某個鏈接
          function openUrlCurrentTab(url)
          {
            getCurrentTabId(tabId=> {
              chrome.tabs.update(tabId, {url: url});
            })
          }

          5.8. 桌面通知

          Chrome提供了一個chrome.notificationsAPI以便插件推送桌面通知,暫未找到chrome.notifications和HTML5自帶的Notification的顯著區別及優勢。

          在后臺JS中,無論是使用chrome.notifications還是Notification都不需要申請權限(HTML5方式需要申請權限),直接使用即可。

          最簡單的通知:

          代碼:

          chrome.notifications.create(null, {
            type: 'basic',
            iconUrl: 'img/icon.png',
            title: '這是標題',
            message: '您剛才點擊了自定義右鍵菜單!'
          });

          通知的樣式可以很豐富:

          這個沒有深入研究,有需要的可以去看官方文檔。

          六、5種類型的JS對比

          Chrome插件的JS主要可以分為這5類:injected script、content-script、popup js、background js和devtools js,

          6.1. 權限對比

          JS種類

          可訪問的API

          DOM訪問情況

          JS訪問情況

          直接跨域

          injected script

          和普通JS無任何差別,不能訪問任何擴展API

          可以訪問

          可以訪問

          不可以

          content script

          只能訪問 extension、runtime等部分API

          可以訪問

          不可以

          不可以

          popup js

          可訪問絕大部分API,除了devtools系列

          不可直接訪問

          不可以

          可以

          background js

          可訪問絕大部分API,除了devtools系列

          不可直接訪問

          不可以

          可以

          devtools js

          只能訪問 devtools、extension、runtime等部分API

          可以

          可以

          不可以


          6.2. 調試方式對比

          JS類型

          調試方式

          圖片說明

          injected script

          直接普通的F12即可

          懶得截圖

          content-script

          打開Console,如圖切換

          popup-js

          popup頁面右鍵審查元素

          background

          插件管理頁點擊背景頁即可

          devtools-js

          暫未找到有效方法

          -


          七、消息通信

          通信主頁:https://developer.chrome.com/extensions/messaging

          前面我們介紹了Chrome插件中存在的5種JS,那么它們之間如何互相通信呢?下面先來系統概況一下,然后再分類細說。需要知道的是,popup和background其實幾乎可以視為一種東西,因為它們可訪問的API都一樣、通信機制一樣、都可以跨域。

          7.1. 互相通信概覽

          注:-表示不存在或者無意義,或者待驗證。


          injected-script

          content-script

          popup-js

          background-js

          injected-script

          -

          window.postMessage

          -

          -

          content-script

          window.postMessage

          -

          chrome.runtime.sendMessage chrome.runtime.connect

          chrome.runtime.sendMessage chrome.runtime.connect

          popup-js

          -

          chrome.tabs.sendMessage chrome.tabs.connect

          -

          chrome.extension. getBackgroundPage()

          background-js

          -

          chrome.tabs.sendMessage chrome.tabs.connect

          chrome.extension.getViews

          -

          devtools-js

          chrome.devtools. inspectedWindow.eval

          -

          chrome.runtime.sendMessage

          chrome.runtime.sendMessage


          7.2. 通信詳細介紹

          7.2.1. popup和background

          popup可以直接調用background中的JS方法,也可以直接訪問background的DOM:

          // background.js
          function test()
          {
            alert('我是background!');
          }
          
          
          // popup.js
          var bg=chrome.extension.getBackgroundPage();
          bg.test(); // 訪問bg的函數
          alert(bg.document.body.innerHTML); // 訪問bg的DOM

          小插曲,今天碰到一個情況,發現popup無法獲取background的任何方法,找了半天才發現是因為background的js報錯了,而你如果不主動查看background的js的話,是看不到錯誤信息的,特此提醒。

          至于background訪問popup如下(前提是popup已經打開):

          var views=chrome.extension.getViews({type:'popup'});
          if(views.length > 0) {
            console.log(views[0].location.href);
          }

          7.2.2. popup或者bg向content主動發送消息

          background.js或者popup.js:

          function 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);
              });
            });
          }
          sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
          {
            console.log('來自content的回復:'+response);
          });

          content-script.js接收:

          chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
          {
            // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
            if(request.cmd=='test') alert(request.value);
            sendResponse('我收到了你的消息!');
          });
          

          雙方通信直接發送的都是JSON對象,不是JSON字符串,所以無需解析,很方便(當然也可以直接發送字符串)。

          網上有些老代碼中用的是chrome.extension.onMessage,沒有完全查清二者的區別(貌似是別名),但是建議統一使用chrome.runtime.onMessage。


          7.2.3. content-script主動發消息給后臺

          content-script.js:

          chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主動發消息給后臺!'}, function(response) {
            console.log('收到來自后臺的回復:' + response);
          });

          background.js 或者 popup.js:

          
          
          // 監聽來自content-script的消息
          chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
          {
            console.log('收到來自content-script的消息:');
            console.log(request, sender, sendResponse);
            sendResponse('我是后臺,我已收到你的消息:' + JSON.stringify(request));
          });
          

          注意事項:

          1.content_scripts向popup主動發消息的前提是popup必須打開!否則需要利用background作中轉;

          2.如果background和popup同時監聽,那么它們都可以同時收到消息,但是只有一個可以sendResponse,一個先發送了,那么另外一個再發送就無效;

          7.2.4. injected script和content-script

          content-script和頁面內的腳本(injected-script自然也屬于頁面內的腳本)之間唯一共享的東西就是頁面的DOM元素,有2種方法可以實現二者通訊:

          1.可以通過window.postMessage和window.addEventListener來實現二者消息通訊;

          2.通過自定義DOM事件來實現;

          第一種方法(推薦):

          injected-script中:

          window.postMessage({"test": '你好!'}, '*');

          content script中:

          window.addEventListener("message", function(e)
          {
            console.log(e.data);
          }, false);

          第二種方法:

          injected-script中:

          var customEvent=document.createEvent('Event');
          customEvent.initEvent('myCustomEvent', true, true);
          function fireCustomEvent(data) {
            hiddenDiv=document.getElementById('myCustomEventDiv');
            hiddenDiv.innerText=data
            hiddenDiv.dispatchEvent(customEvent);
          }
          fireCustomEvent('你好,我是普通JS!');

          content-script.js中:

          var hiddenDiv=document.getElementById('myCustomEventDiv');
          if(!hiddenDiv) {
            hiddenDiv=document.createElement('div');
            hiddenDiv.style.display='none';
            document.body.appendChild(hiddenDiv);
          }
          hiddenDiv.addEventListener('myCustomEvent', function() {
            var eventData=document.getElementById('myCustomEventDiv').innerText;
            console.log('收到自定義事件消息:' + eventData);
          });


          7.3. 長連接和短連接

          其實上面已經涉及到了,這里再單獨說明一下。Chrome插件中有2種通信方式,一個是短連接(chrome.tabs.sendMessage和chrome.runtime.sendMessage),一個是長連接(chrome.tabs.connect和chrome.runtime.connect)。

          短連接的話就是擠牙膏一樣,我發送一下,你收到了再回復一下,如果對方不回復,你只能重新發,而長連接類似WebSocket會一直建立連接,雙方可以隨時互發消息。

          短連接上面已經有代碼示例了,這里只講一下長連接。

          popup.js:

          getCurrentTabId((tabId)=> {
            var port=chrome.tabs.connect(tabId, {name: 'test-connect'});
            port.postMessage({question: '你是誰啊?'});
            port.onMessage.addListener(function(msg) {
              alert('收到消息:'+msg.answer);
              if(msg.answer && msg.answer.startsWith('我是'))
              {
                port.postMessage({question: '哦,原來是你啊!'});
              }
            });
          });

          content-script.js:

          // 監聽長連接
          chrome.runtime.onConnect.addListener(function(port) {
            console.log(port);
            if(port.name=='test-connect') {
              port.onMessage.addListener(function(msg) {
                console.log('收到長連接消息:', msg);
                if(msg.question=='你是誰啊?') port.postMessage({answer: '我是你爸!'});
              });
            }
          });


          八、其它補充

          8.1. 動態注入或執行JS

          雖然在background和popup中無法直接訪問頁面DOM,但是可以通過chrome.tabs.executeScript來執行腳本,從而實現訪問web頁面的DOM(注意,這種方式也不能直接訪問頁面JS)。

          示例manifest.json配置:

          {
            "name": "動態JS注入演示",
            ...
            "permissions": [
              "tabs", "http://*/*", "https://*/*"
            ],
            ...
          }

          JS:

          // 動態執行JS代碼
          chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
          // 動態執行JS文件
          chrome.tabs.executeScript(tabId, {file: 'some-script.js'});
          

          8.2. 動態注入CSS

          示例manifest.json配置:

          {
            "name": "動態CSS注入演示",
            ...
            "permissions": [
              "tabs", "http://*/*", "https://*/*"
            ],
            ...
          }

          JS代碼:

          // 動態執行CSS代碼,TODO,這里有待驗證
          chrome.tabs.insertCSS(tabId, {code: 'xxx'});
          // 動態執行CSS文件
          chrome.tabs.insertCSS(tabId, {file: 'some-style.css'});

          8.3. 獲取當前窗口ID

          chrome.windows.getCurrent(function(currentWindow)
          {
            console.log('當前窗口ID:' + currentWindow.id);
          });

          8.4. 獲取當前標簽頁ID

          一般有2種方法:

          // 獲取當前選項卡ID
          function getCurrentTabId(callback)
          {
            chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
          {
              if(callback) callback(tabs.length ? tabs[0].id: null);
            });
          }

          獲取當前選項卡id的另一種方法,大部分時候都類似,只有少部分時候會不一樣(例如當窗口最小化時)

          // 獲取當前選項卡ID
          function getCurrentTabId2()
          {
            chrome.windows.getCurrent(function(currentWindow)
          {
              chrome.tabs.query({active: true, windowId: currentWindow.id}, function(tabs)
          {
                if(callback) callback(tabs.length ? tabs[0].id: null);
              });
            });
          }
          

          8.5. 本地存儲

          本地存儲建議用chrome.storage而不是普通的localStorage,區別有好幾點,個人認為最重要的2點區別是:

          chrome.storage是針對插件全局的,即使你在background中保存的數據,在content-script也能獲取到;

          chrome.storage.sync可以跟隨當前登錄用戶自動同步,這臺電腦修改的設置會自動同步到其它電腦,很方便,如果沒有登錄或者未聯網則先保存到本地,等登錄了再同步至網絡;

          需要聲明storage權限,有chrome.storage.sync和chrome.storage.local2種方式可供選擇,使用示例如下:

          // 讀取數據,第一個參數是指定要讀取的key以及設置默認值
          chrome.storage.sync.get({color: 'red', age: 18}, function(items) {
            console.log(items.color, items.age);
          });
          // 保存數據
          chrome.storage.sync.set({color: 'blue'}, function() {
            console.log('保存成功!');
          });

          8.6. webRequest

          通過webRequest系列API可以對HTTP請求進行任性地修改、定制,這里通過beforeRequest來簡單演示一下它的冰山一角:

          //manifest.json
          {
            // 權限申請
            "permissions":
            [
              "webRequest", // web請求
              "webRequestBlocking", // 阻塞式web請求
              "storage", // 插件本地存儲
              "http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
              "https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
            ],
          }
          
          
          // background.js
          // 是否顯示圖片
          var showImage;
          chrome.storage.sync.get({showImage: true}, function(items) {
            showImage=items.showImage;
          });
          // web請求監聽,最后一個參數表示阻塞式,需單獨聲明權限:webRequestBlocking
          chrome.webRequest.onBeforeRequest.addListener(details=> {
            // cancel 表示取消本次請求
            if(!showImage && details.type=='image') return {cancel: true};
            // 簡單的音視頻檢測
            // 大部分網站視頻的type并不是media,且視頻做了防下載處理,所以這里僅僅是為了演示效果,無實際意義
            if(details.type=='media') {
              chrome.notifications.create(null, {
                type: 'basic',
                iconUrl: 'img/icon.png',
                title: '檢測到音視頻',
                message: '音視頻地址:' + details.url,
              });
            }
          }, {urls: ["<all_urls>"]}, ["blocking"]);
          

          8.7. 國際化

          插件根目錄新建一個名為_locales的文件夾,再在下面新建一些語言的文件夾,如en、zh_CN、zh_TW,然后再在每個文件夾放入一個messages.json,同時必須在清單文件中設置default_locale。

          _locales\en\messages.json內容:

          {
            "pluginDesc": {"message": "A simple chrome extension demo"},
            "helloWorld": {"message": "Hello World!"}
          }

          _locales\zh_CN\messages.json內容:

          {
            "pluginDesc": {"message": "一個簡單的Chrome插件demo"},
            "helloWorld": {"message": "你好啊,世界!"}
          }

          在manifest.json和CSS文件中通過__MSG_messagename__引入,如:

          {
            "description": "__MSG_pluginDesc__",
            // 默認語言
            "default_locale": "zh_CN",
          }

          JS中則直接chrome.i18n.getMessage("helloWorld")。

          測試時,通過給chrome建立一個不同的快捷方式chrome.exe --lang=en來切換語言,如:

          英文效果:

          中文效果:


          8.8. API總結

          比較常用用的一些API系列:

          chrome.tabs

          chrome.runtime

          chrome.webRequest

          chrome.window

          chrome.storage

          chrome.contextMenus

          chrome.devtools

          chrome.extension


          九、經驗總結

          9.1. 查看已安裝插件路徑

          已安裝的插件源碼路徑:C:\Users\用戶名\AppData\Local\Google\Chrome\User Data\Default\Extensions,每一個插件被放在以插件ID為名的文件夾里面,想要學習某個插件的某個功能是如何實現的,看人家的源碼是最好的方法了:

          如何查看某個插件的ID?進入 chrome://extensions ,然后勾線開發者模式即可看到了。


          9.2. 特別注意background的報錯

          很多時候你發現你的代碼會莫名其妙的失效,找來找去又找不到原因,這時打開background的控制臺才發現原來某個地方寫錯了導致代碼沒生效,正式由于background報錯的隱蔽性(需要主動打開對應的控制臺才能看到錯誤),所以特別注意這點。


          9.3. 如何讓popup頁面不關閉

          在對popup頁面審查元素的時候popup會被強制打開無法關閉,只有控制臺關閉了才可以關閉popup,原因很簡單:如果popup關閉了控制臺就沒用了。這種方法在某些情況下很實用!


          9.4. 不支持內聯JavaScript的執行

          也就是不支持將js直接寫在html中,比如:

          <input id="btn" type="button" value="收藏" onclick="test()"/>

          報錯如下:

          Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

          解決方法就是用JS綁定事件:

          $('#btn').on('click', function(){alert('測試')});

          另外,對于A標簽,這樣寫href="javascript:;"然后用JS綁定事件雖然控制臺會報錯,但是不受影響,當然強迫癥患者受不了的話只能寫成href="#"了。

          如果這樣寫:

          <a href="javascript:;" id="get_secret">請求secret</a>

          報錯如下:

          Refused to execute JavaScript URL because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

          9.5. 注入CSS的時候必須小心

          由于通過content_scripts注入的CSS優先級非常高,幾乎僅次于瀏覽器默認樣式,稍不注意可能就會影響一些網站的展示效果,所以盡量不要寫一些影響全局的樣式。

          之所以強調這個,是因為這個帶來的問題非常隱蔽,不太容易找到,可能你正在寫某個網頁,昨天樣式還是好好的,怎么今天就突然不行了?然后你辛辛苦苦找來找去,找了半天才發現竟然是因為插件里面的一個樣式影響的!


          十、打包與發布

          打包的話直接在插件管理頁有一個打包按鈕:

          然后會生成一個.crx文件,要發布到Google應用商店的話需要先登錄你的Google賬號,然后花5個$注冊為開發者,本人太窮,就懶得親自驗證了,有發布需求的自己去整吧。


          十一、參考

          11.1. 官方資料

          推薦查看官方文檔,雖然是英文,但是全且新,國內的中文資料都比較舊(注意以下全部需要FQ):

          Chrome插件官方文檔主頁

          Chrome插件官方示例

          manifest清單文件

          permissions權限

          chrome.xxx.api文檔

          模糊匹配規則語法詳解

          11.2. 第三方資料

          部分中文資料,不是特別推薦:

          360安全瀏覽器開發文檔

          360極速瀏覽器Chrome擴展開發文檔

          Chrome擴展開發極客系列博客

          本文轉載自作者小茗同學

          • 電腦桌面上點擊鼠標右鍵,選擇“個性化”。


          • 然后在頁面中選擇“顯示”;




          • 接下來選擇“125%”,然后就可以調整屏幕上的文字大小了。



          調整網頁頁面字體大小:

          在瀏覽器打開一個頁面,同時按住鍵盤Ctrl鍵+鼠標滾輪(或者鍵盤“+/-”),即可調整網頁字體大小;

          還有一種方法是點擊網頁上的
          工具欄圖標,點擊網頁縮放,選擇相應的字體大小進行調整。

          (本文轉發自 http://www.mengniukeji.xyz/1/132.html 商業轉載請聯系作者獲得授權,非商業轉載請注明出處)

          篇文章介紹CSS3中增強用戶界面的關于溢出、縮放、輪廓、鼠標事件、文本選中等屬性的使用。這些屬性基本上得到了大部分瀏覽器的支持,可以在代碼中直接書寫。

          承接文章:CSS3盒模型尺寸計算方法,box-sizing屬性的使用,你知道嗎?

          技術等級:中級 | 適合有一定的CSS基礎的人士閱讀。

          希望收藏了這篇文章的你同時也可以關注一下“小海前端”的頭條號,因為這些文章都是連載的,并且是經過系統的歸納和總結的。塌下心來認真閱讀,你一定會學到對你有用的知識。

          CSS3所提供的增強用戶界面的屬性以及與此相關的屬性比較多,小海前端(頭條號)將分幾次對其進行講解。所包含的所有增強用戶界面的屬性包括:

          • box-shadow

          • box-sizing

          • overflow-x

          • overflow-y

          • resize

          • outline

          • outline-width

          • outline-style

          • outline-color

          • outline-offset

          • pointer-events

          • user-select

          • appearance

          • content

          • counter-increment

          • counter-reset

          • quotes

          本篇文章為大家講解上述粗體顯示的五組增強用戶界面屬性。

          一、全新的溢出設置:

          CSS3技術將原有的overflow屬性劃分為兩個方向的溢出屬性。

          • overflow-x,設置水平方向的溢出解決方案。

          • overflow-y,設置垂直方向的溢出解決方案。

          這兩個屬性的取值和overflow屬性相同,可以取如下值:

          • auto,自動,當發生溢出時,才出現滾動條。

          • visible,溢出的部分依然可見。

          • hiddle,溢出的部分隱藏。

          • scroll,若發生溢出,則強制顯示滾動條。

          如果小伙伴們有不熟悉overflow屬性的用法的,可以參看小海前端(頭條號)寫的《CSS三個重要的定位屬性,使用頻率超高,定位屬性詳解》一文。

          二、任意元素都可以縮放:

          什么叫做元素縮放呢?我們來看小伙伴們都非常熟悉的表單元素:多行文本域。

          <textarea name=”introduce” rows=”10” cols=”30”></textarea>

          下圖為我們展示了多行文本域在Google Chrome瀏覽器中的外觀。

          多行文本域自帶縮放調整功能

          我們可以清晰地看出,在多行文本域的右下角有一個可以用來調整控件尺寸的位置圖標,用戶利用鼠標拖拽該圖標就可以改變多行文本域的顯示大小。

          如果希望一個普通的塊級元素也具備該拖拽圖標,那應該怎么解決呢?

          CSS3提供了resize屬性來完成該功能。

          CSS3技術使用resize屬性來設置對象是否具備縮放功能

          該屬性可以取如下值:

          • none,塊級元素不具備右下角拖拽圖標。

          • horizontal,塊級元素具備只可以改變水平方向寬度的拖拽圖標。

          • vertical,塊級元素具備只可以改變垂直方向高度的拖拽圖標。

          • both,塊級元素具備可以同時改變寬度和高度的拖拽圖標。

          1、該屬性使用時必須結合溢出屬性。

          要想讓右下角的拖拽圖標生效,必須設置塊級元素的溢出屬性,并且溢出屬性的取值還不得為visible。

          2、可以結合下列CSS3屬性來控制塊級元素拖拽的尺寸范圍。

          • min-width,設置可拖拽范圍的最小寬度。

          • max-width,設置可拖拽范圍的最大寬度。

          • min-height,設置可拖拽范圍的最小高度。

          • max-height,設置可拖拽范圍的最大高度。

          讓我們通過一個簡單的實例來實現塊級元素的縮放效果。

          例:在頁面中有一個<div></div>塊級元素,其id屬性取值為box,內部包含任意文本。設置該塊級元素的寬度為200像素,高度為200像素。通過CSS3設置要求該塊級元素能夠自由縮放,且寬度和高度不超過400像素。

          CSS代碼如下所示:

          #box{

          width:200px; height:200px;

          overflow:hidden;

          resize:both;

          max-width:400px;

          max-height:400px;

          }

          三、元素本身的輪廓:

          什么是元素輪廓呢?我們來看小伙伴們都非常熟悉的表單元素:文本域。

          <input type=”text” name=”nick” />

          下圖為我們展示了文本域在Google Chrome瀏覽器中獲得鼠標焦點時的外觀。

          文本框獲得焦點時顯示的對象輪廓

          我們可以清晰地看出,當文本域獲得鼠標焦點時,在其邊框位置處又一圈淡藍色的線條對其進行包裹,這就是文本框的輪廓。

          CSS3提供了outline及其派生屬性來調整元素的輪廓,其派生屬性包括:

          • outline-width,設置元素的輪廓寬度。

          • outline-color,設置元素的輪廓顏色。

          • outline-style,設置元素的輪廓樣式。

          除此之外,CSS3還提供了用來設置輪廓偏移的屬性:

          loutline-offset,該屬性不是outline屬性的派生屬性,因此必須單獨使用。

          屬性outline的取值格式如下所示:

          outline:<outline-style> <outline-width> <outline-color>

          要想去掉表單元素中自帶的輪廓屬性,可以設置outline屬性的取值為none。

          input[type=text]:focus{

          outline:none;

          }

          上段代碼設置當type屬性取值為text的<input />標記獲得鼠標焦點時,將其自帶的輪廓隱藏。

          例:我們來為一個塊級元素設置輪廓效果。

          #box{

          outline:solid 1px #7a9cd3;

          outline-offset:10px;

          }

          下圖為我們展示了上段CSS代碼中輪廓的效果。

          輪廓線的偏移效果

          默認情況下,輪廓會占據邊框的位置。由于outline-offset屬性的設置,可以看出,輪廓從邊框位置向外偏移了10像素的距離。

          四、對元素取消鼠標事件:

          CSS3提供了一個名為pointer-events的屬性,用來設置對象不能成為鼠標事件的目標。

          該屬性的取值較多,但是大部分取值主流瀏覽器尚不支持,當該屬性取值為none時,對象就不再是鼠標事件的目標了。

          CSS3技術使用pointer-events屬性取值none來取消元素的鼠標事件

          若設置了對象不再是鼠標的事件目標,則該對象上綁定的事件代碼將不再生效。鼠標單擊該對象時,這個鼠標動作會自動作用在該對象下方的對象上。

          五、是否允許選中文本:

          CSS3提供了一個名為user-select的屬性,用來設置在頁面中是否允許文本被選中。

          CSS3技術使用user-select屬性設置是否允許文本被選中

          該屬性可以取如下值:

          • none,對象內部的文本不允許被選中。

          • text,對象內部的文本允許被選中。

          • all,對象內部的文本只允許作為一個整體被選中。

          在CSS2時代,要想讓頁面文本不允許被鼠標選中,只能使用JavaScript技術編寫代碼。有了user-select屬性,實現這一效果變得更加簡單。

          小海聲明

          在頭條上發表的這些文章都是從前端開發的基礎開始一步一步講起的。我非常希望能有更多的前端開發初學者通過我寫的文章,逐步學到一定的知識,甚至慢慢有了入門的感覺。這些文章都是我這幾年教學過程中的經驗,每寫一篇時我都盡量把握好措辭,用簡單易懂的語言描述,同時精心設計版面,讓版面更加豐富,激發閱讀興趣。所以,每一篇文章可能篇幅不長,但是都要耗費小海老師很久的時間。

          希望收藏了這篇文章的你同時也可以關注一下“小海前端”的頭條號,因為這些文章都是連載的,并且是經過系統的歸納和總結的。塌下心來認真閱讀,你一定會學到對你有用的知識。

          關注“小海前端”,我會繼續為大家奉上更加深入的前端開發文章,也希望更多的初學者跟著學下去,我們共同將前端開發的路努力堅持的走下去。

          文章預告

          明天的文章中,將為大家講解剩下的增強用戶界面的屬性,包括appearance和content屬性的使用。


          主站蜘蛛池模板: 国产精品视频免费一区二区三区| 国产一区二区三区在线| 亚洲制服丝袜一区二区三区| 亚洲日本一区二区三区在线| 久久久国产精品一区二区18禁| 久久国产精品视频一区| 精品无码一区在线观看| 在线播放偷拍一区精品| 国产乱子伦一区二区三区| 中文字幕一区二区区免| 秋霞无码一区二区| 国产成人久久一区二区不卡三区 | 无码中文人妻在线一区| 国产在线精品一区二区在线看 | 亚洲国产专区一区| 亚洲AV色香蕉一区二区| 久久精品无码一区二区app | 丰满人妻一区二区三区免费视频| 亚洲精品色播一区二区| 日本无卡码一区二区三区| 亚洲国产一区国产亚洲 | 人妻体内射精一区二区三区| 日韩有码一区二区| 天天爽夜夜爽人人爽一区二区| 亚洲AV无码一区二区三区鸳鸯影院| 一区二区三区在线|欧| 亚洲国产精品一区二区第一页| 亚洲国产视频一区| 日本高清无卡码一区二区久久 | 91福利国产在线观看一区二区| 国产成人亚洲综合一区| 国产午夜精品一区二区| 成人精品一区二区户外勾搭野战| 亚洲av区一区二区三| 久久久久人妻一区精品色| 精品无码一区二区三区水蜜桃| 少妇激情AV一区二区三区| 日本一区二区三区中文字幕| 亚洲一区中文字幕在线观看| 国产情侣一区二区三区| 无码人妻一区二区三区免费|