時(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 的能力來做這件事。"browser_action": { "default_icon": "images/ic_black_16.png", "default_title": "GIF 截圖", "default_popup": "popup.html"},
這個(gè) html 可以引用第三方的 js,css。為了方便后續(xù)界面繪制,我們引入 jquery,select2。"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)行截圖操作。// 右鍵菜單演示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),就是 遮罩層如何做比較合適?。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)行裁剪。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。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)建、填充圖形、給圖形填充漸變顏色。 組合圖形(包括組合圖形、圖形文字、圖片等)等一系列功能。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; ... }});
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) } })}
關(guān)鍵詞:把手
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。