时间:2023-05-16 07:18:02 | 来源:网站运营
时间:2023-05-16 07:18:02 来源:网站运营
手把手教你用 Chrome 制作 GIF 截图:最近想要作一个网页端的截图工具,用于提 Issue 的时候能更方便的附图。为了能体现操作步骤,最好是能截动图,也就是能生成 GIF。纯粹的靠引入 JS SDK 实现有点不现实,好在我们内部统一使用 Chrome,可以利用 Chrome Extension 的能力来做这件事。"browser_action": { "default_icon": "images/ic_black_16.png", "default_title": "GIF 截图", "default_popup": "popup.html"},
这个 html 可以引用第三方的 js,css。为了方便后续界面绘制,我们引入 jquery,select2。"permissions": [ "storage"]
这样我们就能使用 chrome.storage.sync 或者是 chrome.storage.local 这两种储存区域了。两种储存区域的区别在于,sync 储存的区域会自动将数据同步到 Google 账户中。local 则少了这个过程。在离线情况下,两种储存的表现一致。// 画质数据初始化chrome.storage.sync.get({quality: 10}, function(items) { const quality = items.quality; qualityEle.val(quality).trigger('change');});// 画质数据变更qualityEle.on("select2:select",function(e){ const quality = e.target.value; chrome.storage.sync.set({quality});});
content-scripts 和 background 都可以拿到这里存储的数据,后续会根据配置的参数进行截图操作。// 右键菜单演示chrome.contextMenus.create({ title: "GIF 截图", onclick: function(){ sendMessageToContentScript({cmd: 'prepare capture'}); }});// 发送信息到 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 文件注入的时机,"document_start" 表示在 css 文件之后,dom 构建或其他脚本运行之前,注入 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 中监听来自 background 的消息。为当前页面生成一个遮罩层,用户在遮罩层上进行框选,框选区域为需要截图的区域。chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { ...});
这里有一个比较有意思的点,就是 遮罩层如何做比较合适?。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 截图场景下,我们需要多次调用 截屏 API。比如当帧数设置为 20 的时候,我们设置的定时器会在 1s 中发送 20 个请求。这时候用长连接比较合适。长连接类似 WebSocket 会一直建立连接,双方可以随时互发消息。长连接使用 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) { //发送消息 port.postMessage({cmd: "start recording", params: { width: clientWidth, height: clientHeight, quality, frame }}); // 定时发送截图信息 recordTimer = setInterval(recordingFrame, 1e3 / frame); });});
接下来就是 background 的部分,之前提过,background 的权限很高,生命周期很长,核心的功能一般写在这里。当然,使用截屏 API(chrome.tabs.captureVisibleTab)之前需要在 manifest.json 中申请 tabs 权限。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 用于捕获页面的可见区域,也就是截屏。这个 API 不支持获取一部分屏幕元素,因此,我们获得的是整个屏幕的数据,需要进行裁剪。context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
drawImage 方法参数比较多,img 表示要使用的图像、画布或视频;sx 表示开始剪切的 x 坐标位置;sy 表示开始剪切的 y 坐标位置;swidth 表示被剪切图像的宽度;sheight 表示被剪切图像的高度;x 表示在画布上放置图像的 x 坐标位置;y 表示在画布上放置图像的 y 坐标位置。width 表示要使用的图像的宽度;height 表示要使用的图像的高度。// 图片切割function slice(dataUrl, sx, sy, sWidth, sHeight) { return new Promise((resolve, reject) => { try { // 创建画布 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 转变为 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 的调用是比较消耗性能的,一般而言,如果生成 png 图片的话,大概需要 70-80 ms,而生成 jpg 只需要 30 - 40 ms。 默认情况下就是生成 jpg 图片。由于这两者性能存在巨大差异,导致我们生成 png 图的话,每秒调用次数最好不要超过 10,而 jpg 可以达到 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 比较基础,我们使用一个 Canvas 库 fabric 来实现图片编辑。Fabric 是一个强大而简单的 JS Canvas 库,我们能通过使用它实现在 Canvas 上创建、填充图形、给图形填充渐变颜色。 组合图形(包括组合图形、图形文字、图片等)等一系列功能。fabricCanvas.on('mouse:down', (opt) => { // 当前位置 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);}
鼠标移动的时候根据鼠标当前位置更新矩形的大小。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(); }}
鼠标离开的时候完成矩形添加,移除操作标记。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) => { // 一帧时长 1e3 / frame gifWorker.addFrame(img, {delay: 1e3 / frame}); }); //最后生成一个blob对象 gifWorker.on('finished', function(blob) { resolve(URL.createObjectURL(blob)); }); gifWorker.render(); } catch (e) { reject(e) } })}
关键词:把手