国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運(yùn)營 > 手把手教你用 Chrome 制作 GIF 截圖

手把手教你用 Chrome 制作 GIF 截圖

時(shí)間:2023-05-16 07:18:02 | 來源:網(wǎng)站運(yùn)營

時(shí)間:2023-05-16 07:18:02 來源:網(wǎng)站運(yùn)營

手把手教你用 Chrome 制作 GIF 截圖:最近想要作一個(gè)網(wǎng)頁端的截圖工具,用于提 Issue 的時(shí)候能更方便的附圖。為了能體現(xiàn)操作步驟,最好是能截動圖,也就是能生成 GIF。純粹的靠引入 JS SDK 實(shí)現(xiàn)有點(diǎn)不現(xiàn)實(shí),好在我們內(nèi)部統(tǒng)一使用 Chrome,可以利用 Chrome Extension 的能力來做這件事。

本文會重點(diǎn)闡述以下幾點(diǎn)內(nèi)容:

  1. 截圖方案對比,說一說 html2canvas 與 Chrome Extension 的優(yōu)劣。
  2. 了解 Chrome Extension 幾個(gè)概念。
  3. 如何實(shí)現(xiàn)區(qū)域框選以及圖片裁剪。
  4. GIF 是如何合成的。
  5. 如何用 canvas 實(shí)現(xiàn)圖片編輯器,包括文字添加,形狀添加,自由繪制等。

html2canvas 方案

要在頁面上生成截圖,我們可以借助 html2canvas 庫,首先將需要裝換的 dom 結(jié)構(gòu)裝換為 canvas,再將 canvas 裝換為圖片。這也是我最早考慮的方案。

這個(gè)方案本身沒毛病,但是在實(shí)際中發(fā)現(xiàn),html2canvas 無法保證所有的 CSS 都能得到正確的渲染,html2canvas 的 官網(wǎng) 也明確列出了目前還不支持的 CSS 樣式。還有一點(diǎn)就是 html2canvas 需要額外處理跨域圖片。

如果是網(wǎng)站內(nèi)部做截圖的話,這個(gè)方案是比較合適的,CSS 的問題可控,圖片可以使用代理。但是作為通用工具的話,樣式以及圖片的問題都無法保證,這個(gè)方案就有點(diǎn)差強(qiáng)人意。

Chrome Extension 方案

Chrome Extension 提供了另外一種制作工具思路。

首先,Chrome Extension 可以運(yùn)行在任意的頁面上,不需要業(yè)務(wù)系統(tǒng)修改代碼,非常適合做一些通用功能。其次,Chrome Extension 提供了很多的能力,其中就有用于截屏的 API。

從性能上來講,html2canvas 的原理是通過拷貝 dom 節(jié)點(diǎn)以及節(jié)點(diǎn)樣式,將其重新渲染為 canvas,整個(gè)流程執(zhí)行在主線程上。Chrome Extension 運(yùn)行在其獨(dú)立的線程中(這里特指 Chrome Extension 的 background)。而且就截屏操作看,其內(nèi)部調(diào)用的是計(jì)算機(jī)底層能力,性能無疑是更有優(yōu)勢。當(dāng)然,這種方案的弊端也很明顯,功能和平臺強(qiáng)綁定,只適用于支持 Chrome Extension 的平臺, 目前除了可以運(yùn)行在 Chrome 上之外,還可以運(yùn)行在 Edge 以及所有 webkit 內(nèi)核的國產(chǎn)瀏覽器上。

總體而言,這兩種方案適用于不同的場景,html2canvas 更加通用一些,目前也大量用于線上業(yè)務(wù),Chrome Extension 定位在于制作網(wǎng)頁工具,場景比較單一。

Chrome Extension 結(jié)構(gòu)

Chrome Extension 本身是一個(gè)由 html,js 和 css 組成的 web 軟件。比較特殊的點(diǎn)在于它規(guī)定了一些展現(xiàn)方式,通訊方式等。 Chrome Extension 的開發(fā)細(xì)節(jié)就不多加贅述了,這里只列出幾個(gè)后續(xù)提到的概念。

manifest.json:這是 Chrome Extension 最重要也是必不可少的文件,用來配置所有和插件相關(guān)的配置,必須放在根目錄。

content-scripts:指的是 Chrome Extension 向頁面注入的 js,通過注入的 js 代碼我們可以控制頁面的一些行為。content-scripts 可以訪問頁面的 DOM,但是只能調(diào)用少量的 chrome 擴(kuò)展 API,基于這個(gè)特性,content-scripts 一般負(fù)責(zé)展示類工作。

background:Chrome Extension 的核心,幾乎可以調(diào)用所有的 Chrome 擴(kuò)展 API(除了devtools)。它的生命周期是 Chrome Extension 中所有類型頁面中最長的,隨著瀏覽器的打開而打開,隨著瀏覽器的關(guān)閉而關(guān)閉。我們一般在 background 中實(shí)現(xiàn)任務(wù)核心邏輯。

popup:是點(diǎn)擊該 Chrome Extension 圖標(biāo)時(shí)打開的一個(gè)小窗口網(wǎng)頁,焦點(diǎn)離開網(wǎng)頁就立即關(guān)閉,我們一般在這個(gè)頁面做一些參數(shù)的配置。

接下來進(jìn)入正題,除去 manifest.json,整個(gè)項(xiàng)目我們分為四個(gè)部分。分別是參數(shù)配置、區(qū)域框選、生成截圖以及最終的編輯截圖。

參數(shù)配置

大部分的 Chrome Extension 在安裝之后,用戶第一直覺就是點(diǎn)擊圖標(biāo),此時(shí) popup 被激活。

我在 manifest.json 中定義了默認(rèn)的 popup 路徑為 popup.html,我們在根目錄下創(chuàng)建 popup.html,按照正常的 html 寫就可以了。

"browser_action": { "default_icon": "images/ic_black_16.png", "default_title": "GIF 截圖", "default_popup": "popup.html"},這個(gè) html 可以引用第三方的 js,css。為了方便后續(xù)界面繪制,我們引入 jquery,select2。

在這個(gè)頁面中,我們配置兩個(gè)截圖需要的參數(shù)。

  1. 每秒幀數(shù),該參數(shù)決定我們生成 GIF 的平滑度。
  2. 截圖質(zhì)量,決定生成圖片的畫質(zhì)。
popup 的生命周期很短,如何將配置的參數(shù)記錄起來呢?

Chrome 為擴(kuò)展應(yīng)用提供了存儲 API,以便將擴(kuò)展中需要保存的數(shù)據(jù)寫入本地磁盤。首先需要在 manifest.json 申請對應(yīng)的存儲權(quán)限。

"permissions": [ "storage"]這樣我們就能使用 chrome.storage.sync 或者是 chrome.storage.local 這兩種儲存區(qū)域了。兩種儲存區(qū)域的區(qū)別在于,sync 儲存的區(qū)域會自動將數(shù)據(jù)同步到 Google 賬戶中。local 則少了這個(gè)過程。在離線情況下,兩種儲存的表現(xiàn)一致。

// 畫質(zhì)數(shù)據(jù)初始化chrome.storage.sync.get({quality: 10}, function(items) { const quality = items.quality; qualityEle.val(quality).trigger('change');});// 畫質(zhì)數(shù)據(jù)變更qualityEle.on("select2:select",function(e){ const quality = e.target.value; chrome.storage.sync.set({quality});});content-scripts 和 background 都可以拿到這里存儲的數(shù)據(jù),后續(xù)會根據(jù)配置的參數(shù)進(jìn)行截圖操作。

最終配置界面如下所示。

截圖區(qū)域框選

content-scripts 會隨著當(dāng)前頁面一起初始化。content-scripts 可以操作當(dāng)前的 dom,所以截圖區(qū)域框選由 content-scripts 負(fù)責(zé)。

background 會跟隨瀏覽器一起打開,在 background 中,我們可以為右鍵菜單增加自定義欄目(需要 contextMenus 權(quán)限)。如下,我們增加一個(gè)【GIF 截圖】的按鈕,用于向 content-scripts 發(fā)送一條消息。

// 右鍵菜單演示chrome.contextMenus.create({ title: "GIF 截圖", onclick: function(){ sendMessageToContentScript({cmd: 'prepare capture'}); }});// 發(fā)送信息到 ContentScriptfunction sendMessageToContentScript(message, callback) { chrome.tabs.query({active: true}, (tabs) => { chrome.tabs.sendMessage(tabs[0].id, message, (response) => { if(callback) callback(response); }); });}content-scripts 同樣定義在 manifest.json 中,如下定義表示,在任意的頁面都依次注入指定的 js 和 css。run_at 控制 js 文件注入的時(shí)機(jī),"document_start" 表示在 css 文件之后,dom 構(gòu)建或其他腳本運(yùn)行之前,注入 js 文件。

"content_scripts": [ { "matches": ["<all_urls>"], "js": ["libs/jquery/jquery.min.js", "src/content-script.js"], "css": ["css/custom.css"], "run_at": "document_start" }],content_scripts 中監(jiān)聽來自 background 的消息。為當(dāng)前頁面生成一個(gè)遮罩層,用戶在遮罩層上進(jìn)行框選,框選區(qū)域?yàn)樾枰貓D的區(qū)域。

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { ...});這里有一個(gè)比較有意思的點(diǎn),就是 遮罩層如何做比較合適?。

這里的遮罩層和我們做模態(tài)框時(shí)候的遮罩層不太一樣,因?yàn)槟B(tài)框是“浮在”遮罩層之上的,而這里需要的是“鏤空”。常見的做法是在“鏤空”區(qū)域四周繪制四個(gè)矩形作為遮罩層,但是這種做法做起來比較復(fù)雜,而且截圖場景下鏤空區(qū)域會頻繁變化,每次變化我們都需要修改四個(gè)矩形的大小。

我推薦的方案是使用 border + pointer-events 的方式繪制遮罩層。我們先使用一個(gè)透明矩形覆蓋當(dāng)前頁面。然后設(shè)置 border 顏色為遮罩層顏色,通過改變 border-width 在四個(gè)方向上的值,就能實(shí)現(xiàn)遮罩層的變化?,F(xiàn)在唯一的問題就是我們需要讓“鏤空”區(qū)域響應(yīng)事件,我們可以 設(shè)置遮罩層的 pointer-events 為 none,就能阻止當(dāng)前層對點(diǎn)擊、狀態(tài)變化和鼠標(biāo)指針變化的影響,相當(dāng)于當(dāng)前層沒有了。

截圖區(qū)域框選的重點(diǎn)在于處理好 mousedown,mousemove 和 mouseup 這三個(gè)事件。

鼠標(biāo)按下的時(shí)候,需要根據(jù)當(dāng)前鼠標(biāo)所在位置和點(diǎn)擊的元素,區(qū)分接下的行為是創(chuàng)建新的選區(qū),還是更改選區(qū)的大小,或是移動當(dāng)前選區(qū)。若是創(chuàng)建新的選區(qū),則為新的選取綁定拖動事件,截圖事件;若當(dāng)前位置已經(jīng)在選區(qū)上了,判斷是移動還是更新大小。鼠標(biāo)移動的時(shí)候需要判斷當(dāng)前行為以及鼠標(biāo)位置,更新選區(qū)大小或是移動選區(qū);鼠標(biāo)松開后,移除無效事件。 這部分代碼比較瑣碎,實(shí)現(xiàn)的效果如下所示。

我們通過這個(gè)步驟,獲得了截圖區(qū)域,接下來就是將這個(gè)區(qū)域發(fā)送給 background,進(jìn)行截圖了。

普通截圖與 GIF 截圖

使用 PS 做過 GIF 的同學(xué)應(yīng)該都知道,制作 GIF 最簡單的方法就是使用多張圖片合成,原理類似于傳統(tǒng)動畫片。JS 制作 GIF 的原理也是這樣的,根據(jù)我們設(shè)置的幀數(shù)對選區(qū)進(jìn)行多次的截圖,然后通過這些截圖合成一張 GIF。因此,普通截圖和 GIF 截圖背后的原理是一樣的,都是使用的 chrome 擴(kuò)展的截屏 API 實(shí)現(xiàn)。

對于普通截圖,由于只需要調(diào)用一次截屏 API,將選區(qū)的坐標(biāo)作為參數(shù)傳遞給 background。這里使用 chrome.runtime.sendMessage 和 background 進(jìn)行通訊。

chrome.storage.sync.get({quality: 10}, function(items) { const quality = items.quality; const params = { sx: topX + 10, sy: topY + 30, sWidth: bottomX - topX - 20, sHeight: bottomY - topY - 60, quality }; chrome.runtime.sendMessage({cmd: 'capture screen', params});});而 GIF 截圖場景下,我們需要多次調(diào)用 截屏 API。比如當(dāng)幀數(shù)設(shè)置為 20 的時(shí)候,我們設(shè)置的定時(shí)器會在 1s 中發(fā)送 20 個(gè)請求。這時(shí)候用長連接比較合適。長連接類似 WebSocket 會一直建立連接,雙方可以隨時(shí)互發(fā)消息。長連接使用 chrome.tabs.connect 或者是 chrome.runtime.connect。

chrome.storage.sync.get({frame: 10, quality: 10}, function(items) { const frame = items.frame; const quality = items.quality; //通道名稱 port = chrome.runtime.connect({name: "shenmax"}); chrome.storage.sync.get({frame: 10, quality: 10}, function(items) { //發(fā)送消息 port.postMessage({cmd: "start recording", params: { width: clientWidth, height: clientHeight, quality, frame }}); // 定時(shí)發(fā)送截圖信息 recordTimer = setInterval(recordingFrame, 1e3 / frame); });});接下來就是 background 的部分,之前提過,background 的權(quán)限很高,生命周期很長,核心的功能一般寫在這里。當(dāng)然,使用截屏 API(chrome.tabs.captureVisibleTab)之前需要在 manifest.json 中申請 tabs 權(quán)限。

function capture(quality) { return new Promise((resolve, reject) => { try { chrome.tabs.captureVisibleTab(null, { quality : 10 * quality }, function(dataUrl) { resolve(dataUrl) }); } catch (e) { reject(e) } })}chrome.tabs.captureVisibleTab 用于捕獲頁面的可見區(qū)域,也就是截屏。這個(gè) API 不支持獲取一部分屏幕元素,因此,我們獲得的是整個(gè)屏幕的數(shù)據(jù),需要進(jìn)行裁剪。

如何對圖像進(jìn)項(xiàng)裁剪? 我們需要借助 canvas 的能力了,流程如下。

  1. 加載圖片并將圖片繪制到 canvas 中。
  2. 利用 canvas 的 drawImage 函數(shù)來裁剪圖。
  3. 將 canvas 轉(zhuǎn)化為 Image。
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);drawImage 方法參數(shù)比較多,img 表示要使用的圖像、畫布或視頻;sx 表示開始剪切的 x 坐標(biāo)位置;sy 表示開始剪切的 y 坐標(biāo)位置;swidth 表示被剪切圖像的寬度;sheight 表示被剪切圖像的高度;x 表示在畫布上放置圖像的 x 坐標(biāo)位置;y 表示在畫布上放置圖像的 y 坐標(biāo)位置。width 表示要使用的圖像的寬度;height 表示要使用的圖像的高度。

// 圖片切割function slice(dataUrl, sx, sy, sWidth, sHeight) { return new Promise((resolve, reject) => { try { // 創(chuàng)建畫布 let canvas = document.getElementsByTagName('canvas'); if (canvas.length === 0) { canvas = document.createElement("canvas"); } canvas.width = sWidth; canvas.height = sHeight; const context = canvas.getContext("2d"); dataUrl2Img(dataUrl).then((img) => { // 裁剪 canvas context.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight); resolve(canvas); }); } catch (e) { reject(e) } })}// dataUrl 轉(zhuǎn)變?yōu)?imgfunction dataUrl2Img(dataUrl) { return new Promise((resolve, reject) => { try { const img = new Image(); img.src = dataUrl; img.onload = function(){ resolve(img); }; } catch (e) { reject(e) } });}這里要考慮下性能問題,chrome.tabs.captureVisibleTab 的調(diào)用是比較消耗性能的,一般而言,如果生成 png 圖片的話,大概需要 70-80 ms,而生成 jpg 只需要 30 - 40 ms。 默認(rèn)情況下就是生成 jpg 圖片。由于這兩者性能存在巨大差異,導(dǎo)致我們生成 png 圖的話,每秒調(diào)用次數(shù)最好不要超過 10,而 jpg 可以達(dá)到 20。

當(dāng)前我們有了一系列的截圖,并不用急著將其生成 GIF,生成 GIF 的時(shí)機(jī)我們放在圖片下載的時(shí)候。先看圖片編輯。

圖片編輯

基本所有的截圖功能都會帶一個(gè)圖片編輯的能力,方便我們在生成圖片上做一些信息標(biāo)注或是重點(diǎn)框選。如何在網(wǎng)頁上實(shí)現(xiàn)這個(gè)功能呢?

圖片編輯功能依賴于 Canvas 繪圖能力。canvas 支持我們繪制任意的二維圖形。流程是這樣的。

  1. 在當(dāng)前的截圖上方覆蓋一個(gè)等大小的 canvas 元素。
  2. 借助 canvas 的 api 在 canvas 上繪制圖形。
  3. 使用 canvas 的 drawImage 方法將當(dāng)前 canvas 元素和截圖合并。drawImage 方法支持傳入一個(gè) canvas。
  4. 將合并后的 canvas 變成 Image 導(dǎo)出。
GIF 中的每張圖片要依次執(zhí)行上述的步驟三和步驟四。

frameList.forEach((canvas) => { const newCvs = document.createElement("canvas"); newCvs.width = initWidth; newCvs.height = initHeight; newCvs.getContext("2d").drawImage(canvas, 0, 0); newCvs.getContext("2d").drawImage(cvs, 0, 0, initWidth, initHeight); canvasList.push(newCvs);});canvas 原生 API 比較基礎(chǔ),我們使用一個(gè) Canvas 庫 fabric 來實(shí)現(xiàn)圖片編輯。Fabric 是一個(gè)強(qiáng)大而簡單的 JS Canvas 庫,我們能通過使用它實(shí)現(xiàn)在 Canvas 上創(chuàng)建、填充圖形、給圖形填充漸變顏色。 組合圖形(包括組合圖形、圖形文字、圖片等)等一系列功能。

以實(shí)現(xiàn)一個(gè)矩形框?yàn)槔?,我們監(jiān)聽 mouseDown 事件,鼠標(biāo)落下的時(shí)候,使用 fabric 生成一個(gè)有邊框,顏色透明的矩形。

fabricCanvas.on('mouse:down', (opt) => { // 當(dāng)前位置 const pos = opt.absolutePointer; switch (select) { case rectangle: if (!start) { x = pos.x; y = pos.y; start = true; addRectangle(pos); } break; ... }}function addRectangle(pos) { const color = colorConfig.val(); const borderSize = rectangleConfig.val(); const square = new fabric.Rect({ strokeWidth: parseInt(borderSize, 10), stroke: color, editingBorderColor: color, width: 1, height: 1, left: pos.x, top: pos.y, fill: 'transparent', padding: 5, cornerSize: 5, cornerColor: color, rotatingPointOffset: 20, }); fabricCanvas.add(square); fabricCanvas.setActiveObject(square);}鼠標(biāo)移動的時(shí)候根據(jù)鼠標(biāo)當(dāng)前位置更新矩形的大小。

fabricCanvas.on('mouse:move', (opt) => { const pos = opt.absolutePointer; switch (select) { case rectangle: if (start) { resizeRectangle(x, y, pos); } break; default: break }});function resizeRectangle(initX, initY, pos) { const x = Math.min(pos.x, initX), y = Math.min(pos.y, initY), w = Math.abs(pos.x - initX), h = Math.abs(pos.y - initY); const square = fabricCanvas.getActiveObject(); if (square) { square.set('top', y).set('left', x).set('width', w).set('height', h); fabricCanvas.renderAll(); }}鼠標(biāo)離開的時(shí)候完成矩形添加,移除操作標(biāo)記。

fabricCanvas.on('mouse:up', () => { switch (select) { case rectangle: if (start) { const square = fabricCanvas.getActiveObject(); fabricCanvas.add(square); changeOperation(pointer); x = y = 0; start = false; } break; ... }});

生成 GIF

用戶保存的時(shí)候,將之前的全部截圖拼接為一個(gè) GIF。這部分工作依賴于 gif.js。gif.js 是一個(gè)可直接在瀏覽器上運(yùn)行的 JavaScript GIF 編碼器。不僅如此,gif.js 還支持通過 web worker 的方式合成 gif。代碼如下所示。合成 gif 的時(shí)間比較久,建議不要生成太大的 GIF。

function makeGif(imgList) { const gifWorker = new GIF({ workers: 4, quality: 10, dither: true, workerScript:'./libs/gif/gif.worker.js' }); return new Promise((resolve, reject) => { try { imgList.forEach((img) => { // 一幀時(shí)長 1e3 / frame gifWorker.addFrame(img, {delay: 1e3 / frame}); }); //最后生成一個(gè)blob對象 gifWorker.on('finished', function(blob) { resolve(URL.createObjectURL(blob)); }); gifWorker.render(); } catch (e) { reject(e) } })}

總結(jié)

目前 Chrome 上有很多優(yōu)秀的擴(kuò)展,隨著 Chrome 市場份額的擴(kuò)大,Chrome Extension 也逐漸演變?yōu)橐环N解決方案。如果你的公司也在大量的使用 Chrome,可以想想是不是一些場景更適合放在 Chrome Extension 上呢?

最后總結(jié)下使用 Chrome Extension 制作 GIF 插件的流程。

  1. 使用 popup 配置基本參數(shù)。
  2. 在 content-scripts 中實(shí)現(xiàn)截圖區(qū)域選擇。
  3. 發(fā)送消息給 background,background 調(diào)用 chrome.tabs.captureVisibleTab 截取可視區(qū)域。
  4. 利用 canvas drawImage 方法切割圖片。
  5. 使用 fabric 自由繪制圖形,再次使用 canvas drawImage 方法將截圖和 fabric 繪制的圖形進(jìn)行合并。
  6. 使用 gif.js 在 web worker 中快速生成 gif 截圖。
目前還有一點(diǎn)需要做的,就是可以將截圖編輯放在截圖區(qū)域選擇的時(shí)候,這樣能在操作的同時(shí)做一些標(biāo)記,更加方便。

該項(xiàng)目的源碼我放在 GitHub 上。

如果您覺得有所收獲,就請點(diǎn)個(gè)贊吧!

關(guān)鍵詞:把手

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉