用JavaScript實現(xiàn)瀏覽器截圖功能的全過程
最近項目中要實現(xiàn)一個需求:
用戶希望在上傳文件時能夠直接截圖上傳,并且要求能夠截圖瀏覽器外的內(nèi)容。
多方查找之下找到了類似的解決方案,但個人感覺操作上有些抽象,僅供參考。
HTML部分
- 截圖開始的按鈕
<button id="start-screenshot" class="button">開始截圖</button>
- 這里是canvas部分
<div id="screenshot-container"> <canvas id="screenshot-canvas"></canvas> <div class="selection-area" id="selection-area" style="display: none;"></div> <div class="toolbar" id="screenshot-toolbar" style="display: none;"> <button id="confirm-screenshot">確認(rèn)</button> <button id="cancel-screenshot">取消</button> </div> </div>
CSS部分
- 根據(jù)需求自行調(diào)整就好
#screenshot-container { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; } #screenshot-canvas { position: absolute; top: 0; left: 0; cursor: crosshair; } .selection-area { position: absolute; border: 2px dashed #12B7F5; background-color: rgba(18, 183, 245, 0.1); pointer-events: none; } .toolbar { position: absolute; background-color: white; border-radius: 4px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); padding: 5px; display: flex; gap: 5px; } .toolbar button { background-color: #12B7F5; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 14px; } .toolbar button:hover { background-color: #0E9AD7; }
JS部分(原生JS為例)
獲取dom元素
const startButton = document.getElementById('start-screenshot'); // 開始截圖按鈕 const screenshotContainer = document.getElementById('screenshot-container'); // 獲取的圖片展示區(qū) const canvas = document.getElementById('screenshot-canvas'); // canvas const ctx = canvas.getContext('2d'); const selectionArea = document.getElementById('selection-area'); // 截圖區(qū)域 const toolbar = document.getElementById('screenshot-toolbar'); // 截圖時右下角的小彈框 const confirmButton = document.getElementById('confirm-screenshot'); // 確認(rèn)按鈕 const cancelButton = document.getElementById('cancel-screenshot'); // 取消按鈕
變量定義
let isCapturing = false; let isSelecting = false; let startX = 0; let startY = 0; let endX = 0; let endY = 0; let screenCapture = null;
設(shè)置畫布大小
function setCanvasSize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
開始截圖方法
async function startScreenshot() { try { // 請求屏幕捕獲 screenCapture = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: 'always' } }); // 獲取視頻軌道 const videoTrack = screenCapture.getVideoTracks()[0]; // 創(chuàng)建視頻元素以捕獲屏幕 const videoElem = document.createElement('video'); videoElem.srcObject = screenCapture; // 當(dāng)視頻加載完成后,繪制到畫布上 videoElem.onloadedmetadata = () => { videoElem.play(); // 設(shè)置畫布大小 setCanvasSize(); // 繪制視頻幀到畫布 ctx.drawImage(videoElem, 0, 0, canvas.width, canvas.height); // 停止視頻軌道 videoTrack.stop(); // 顯示截圖容器 screenshotContainer.style.display = 'block'; isCapturing = true; }; } catch (err) { console.error('截圖失敗:', err); alert('截圖失敗,請確保您已授予屏幕捕獲權(quán)限。'); } }
這里會請求屏幕捕獲權(quán)限并獲取屏幕內(nèi)容,這里可以選擇
瀏覽器標(biāo)簽頁
、windows打開的窗口
、整個屏幕
,確實可以獲取到瀏覽器之外的內(nèi)容。更新選擇區(qū)域
function updateSelectionArea() { const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); const left = Math.min(startX, endX); const top = Math.min(startY, endY); selectionArea.style.display = 'block'; selectionArea.style.left = left + 'px'; selectionArea.style.top = top + 'px'; selectionArea.style.width = width + 'px'; selectionArea.style.height = height + 'px'; // 更新工具欄位置 toolbar.style.display = 'flex'; toolbar.style.left = (left + width + 5) + 'px'; toolbar.style.top = (top + height + 5) + 'px'; }
確認(rèn)截圖(在這里獲取截圖結(jié)果)
function confirmScreenshot() { if (!isCapturing) return; const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); const left = Math.min(startX, endX); const top = Math.min(startY, endY); // 創(chuàng)建新畫布以保存選定區(qū)域 const resultCanvas = document.createElement('canvas'); resultCanvas.width = width; resultCanvas.height = height; const resultCtx = resultCanvas.getContext('2d'); // 將選定區(qū)域繪制到新畫布 resultCtx.drawImage( canvas, left, top, width, height, 0, 0, width, height ); // 在這里獲取截圖結(jié)果 // 如果想生成成一個Base64url const base64Url = resultCanvas.toDataURL(); // 如果想生成成一個File對象 const resultFile = dataURLtoFile(resultCanvas.toDataURL(), "截圖.png") // 重置截圖狀態(tài) resetScreenshot(); }
將Base64數(shù)據(jù)轉(zhuǎn)換為File對象(不需要轉(zhuǎn)換結(jié)果為文件對象可以不寫這段)
function dataURLtoFile(dataurl, filename) { // 將Base64數(shù)據(jù)拆分為MIME類型和實際數(shù)據(jù) const arr = dataurl.split(','); const mime = arr[0].match(/:(.*?);/)[1]; // 獲取MIME類型 const bstr = atob(arr[1]); // 解碼Base64數(shù)據(jù) let n = bstr.length; const u8arr = new Uint8Array(n); // 將解碼后的數(shù)據(jù)轉(zhuǎn)換為Uint8Array while (n--) { u8arr[n] = bstr.charCodeAt(n); } // 創(chuàng)建并返回File對象 return new File([u8arr], filename, { type: mime }); }
取消截圖
function cancelScreenshot() { resetScreenshot(); }
重置截圖狀態(tài)
function resetScreenshot() { isCapturing = false; isSelecting = false; selectionArea.style.display = 'none'; toolbar.style.display = 'none'; screenshotContainer.style.display = 'none'; ctx.clearRect(0, 0, canvas.width, canvas.height); }
各類監(jiān)聽事件
// 事件監(jiān)聽器 startButton.addEventListener('click', startScreenshot); confirmButton.addEventListener('click', confirmScreenshot); cancelButton.addEventListener('click', cancelScreenshot); // 鼠標(biāo)事件處理 canvas.addEventListener('mousedown', function(e) { if (!isCapturing) return; isSelecting = true; startX = e.clientX; startY = e.clientY; endX = e.clientX; endY = e.clientY; updateSelectionArea(); }); canvas.addEventListener('mousemove', function(e) { if (!isSelecting) return; endX = e.clientX; endY = e.clientY; updateSelectionArea(); }); canvas.addEventListener('mouseup', function() { isSelecting = false; }); // 窗口大小改變時重新設(shè)置畫布大小 window.addEventListener('resize', function() { if (isCapturing) { setCanvasSize(); } });
鍵盤快捷鍵(不需要可以不用)
// 鍵盤快捷鍵 document.addEventListener('keydown', function(e) { // Alt + A 開始截圖 if (e.altKey && e.key === 'a') { e.preventDefault(); startScreenshot(); } // Enter 確認(rèn)截圖 if (e.key === 'Enter' && isCapturing) { confirmScreenshot(); } // Esc 取消截圖 if (e.key === 'Escape' && isCapturing) { cancelScreenshot(); } });
完整代碼
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>截圖</title> <style> body { margin: 0; padding: 0; font-family: 'Microsoft YaHei', sans-serif; background-color: #f5f5f5; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; overflow: hidden; } .container { text-align: center; background-color: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 800px; width: 100%; } .button { background-color: #12B7F5; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; margin-top: 10px; } .button:hover { background-color: #0E9AD7; } #screenshot-container { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; } #screenshot-canvas { position: absolute; top: 0; left: 0; cursor: crosshair; } .selection-area { position: absolute; border: 2px dashed #12B7F5; background-color: rgba(18, 183, 245, 0.1); pointer-events: none; } .toolbar { position: absolute; background-color: white; border-radius: 4px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); padding: 5px; display: flex; gap: 5px; } .toolbar button { background-color: #12B7F5; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 14px; } .toolbar button:hover { background-color: #0E9AD7; } </style> </head> <body> <div class="container"> <button id="start-screenshot" class="button">開始截圖</button> </div> <div id="screenshot-container"> <canvas id="screenshot-canvas"></canvas> <div class="selection-area" id="selection-area" style="display: none;"></div> <div class="toolbar" id="screenshot-toolbar" style="display: none;"> <button id="confirm-screenshot">確認(rèn)</button> <button id="cancel-screenshot">取消</button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 元素引用 const startButton = document.getElementById('start-screenshot'); const screenshotContainer = document.getElementById('screenshot-container'); const canvas = document.getElementById('screenshot-canvas'); const ctx = canvas.getContext('2d'); const selectionArea = document.getElementById('selection-area'); const toolbar = document.getElementById('screenshot-toolbar'); const confirmButton = document.getElementById('confirm-screenshot'); const cancelButton = document.getElementById('cancel-screenshot'); // 截圖狀態(tài) let isCapturing = false; let isSelecting = false; let startX = 0; let startY = 0; let endX = 0; let endY = 0; let screenCapture = null; // 設(shè)置畫布大小 function setCanvasSize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } // 開始截圖 async function startScreenshot() { try { // 請求屏幕捕獲 screenCapture = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: 'always' } }); // 獲取視頻軌道 const videoTrack = screenCapture.getVideoTracks()[0]; // 創(chuàng)建視頻元素以捕獲屏幕 const videoElem = document.createElement('video'); videoElem.srcObject = screenCapture; // 當(dāng)視頻加載完成后,繪制到畫布上 videoElem.onloadedmetadata = () => { videoElem.play(); // 設(shè)置畫布大小 setCanvasSize(); // 繪制視頻幀到畫布 ctx.drawImage(videoElem, 0, 0, canvas.width, canvas.height); // 停止視頻軌道 videoTrack.stop(); // 顯示截圖容器 screenshotContainer.style.display = 'block'; isCapturing = true; }; } catch (err) { console.error('截圖失敗:', err); alert('截圖失敗,請確保您已授予屏幕捕獲權(quán)限。'); } } // 更新選擇區(qū)域 function updateSelectionArea() { const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); const left = Math.min(startX, endX); const top = Math.min(startY, endY); selectionArea.style.display = 'block'; selectionArea.style.left = left + 'px'; selectionArea.style.top = top + 'px'; selectionArea.style.width = width + 'px'; selectionArea.style.height = height + 'px'; // 更新工具欄位置 toolbar.style.display = 'flex'; toolbar.style.left = (left + width + 5) + 'px'; toolbar.style.top = (top + height + 5) + 'px'; } // 將Base64數(shù)據(jù)轉(zhuǎn)換為File對象 function dataURLtoFile(dataurl, filename) { // 將Base64數(shù)據(jù)拆分為MIME類型和實際數(shù)據(jù) const arr = dataurl.split(','); const mime = arr[0].match(/:(.*?);/)[1]; // 獲取MIME類型 const bstr = atob(arr[1]); // 解碼Base64數(shù)據(jù) let n = bstr.length; const u8arr = new Uint8Array(n); // 將解碼后的數(shù)據(jù)轉(zhuǎn)換為Uint8Array while (n--) { u8arr[n] = bstr.charCodeAt(n); } // 創(chuàng)建并返回File對象 return new File([u8arr], filename, { type: mime }); } // 確認(rèn)截圖 function confirmScreenshot() { if (!isCapturing) return; const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); const left = Math.min(startX, endX); const top = Math.min(startY, endY); // 創(chuàng)建新畫布以保存選定區(qū)域 const resultCanvas = document.createElement('canvas'); resultCanvas.width = width; resultCanvas.height = height; const resultCtx = resultCanvas.getContext('2d'); // 將選定區(qū)域繪制到新畫布 resultCtx.drawImage( canvas, left, top, width, height, 0, 0, width, height ); // 在這里獲取截圖結(jié)果 // 如果想生成成一個Base64url const base64Url = resultCanvas.toDataURL(); // 如果想生成成一個File對象 const resultFile = dataURLtoFile(resultCanvas.toDataURL(), "截圖.png") // 重置截圖狀態(tài) resetScreenshot(); } // 取消截圖 function cancelScreenshot() { resetScreenshot(); } // 重置截圖狀態(tài) function resetScreenshot() { isCapturing = false; isSelecting = false; selectionArea.style.display = 'none'; toolbar.style.display = 'none'; screenshotContainer.style.display = 'none'; ctx.clearRect(0, 0, canvas.width, canvas.height); } // 事件監(jiān)聽器 startButton.addEventListener('click', startScreenshot); confirmButton.addEventListener('click', confirmScreenshot); cancelButton.addEventListener('click', cancelScreenshot); // 鼠標(biāo)事件處理 canvas.addEventListener('mousedown', function(e) { if (!isCapturing) return; isSelecting = true; startX = e.clientX; startY = e.clientY; endX = e.clientX; endY = e.clientY; updateSelectionArea(); }); canvas.addEventListener('mousemove', function(e) { if (!isSelecting) return; endX = e.clientX; endY = e.clientY; updateSelectionArea(); }); canvas.addEventListener('mouseup', function() { isSelecting = false; }); // 鍵盤快捷鍵 document.addEventListener('keydown', function(e) { // Alt + A 開始截圖 if (e.altKey && e.key === 'a') { e.preventDefault(); startScreenshot(); } // Enter 確認(rèn)截圖 if (e.key === 'Enter' && isCapturing) { confirmScreenshot(); } // Esc 取消截圖 if (e.key === 'Escape' && isCapturing) { cancelScreenshot(); } }); // 窗口大小改變時重新設(shè)置畫布大小 window.addEventListener('resize', function() { if (isCapturing) { setCanvasSize(); } }); }); </script> </body> </html>
總結(jié)
姑且算是實現(xiàn)了這個需求,但是實現(xiàn)效果并不是很理想,只能說僅供參考吧
到此這篇關(guān)于用JavaScript實現(xiàn)瀏覽器截圖功能的文章就介紹到這了,更多相關(guān)JS瀏覽器截圖功能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Bootstrap輪播插件中圖片變形的終極解決方案 使用jqthumb.js
這篇文章主要介紹了Bootstrap輪播插件中圖片變形的終極解決方案,使用jqthumb.js,感興趣的小伙伴們可以參考一下2016-07-07深入理解JavaScript系列(29):設(shè)計模式之裝飾者模式詳解
這篇文章主要介紹了深入理解JavaScript系列(29):設(shè)計模式之裝飾者模式詳解,裝飾者用用于包裝同接口的對象,不僅允許你向方法添加行為,而且還可以將方法設(shè)置成原始對象調(diào)用(例如裝飾者的構(gòu)造函數(shù)),需要的朋友可以參考下2015-03-03不使用XMLHttpRequest實現(xiàn)異步加載 Iframe和script
運用Iframe和script可以實現(xiàn)簡單的異步加載,沒有使用XMLHttpRequest,需要的朋友可以參考下2012-10-10如何確保JavaScript的執(zhí)行順序 之實戰(zhàn)篇
我曾在文章《如何在多個頁面使用同一個HTML片段 - 續(xù)》的最后提到JavaScript順序執(zhí)行的特性。雖然現(xiàn)代瀏覽器可以并行的下載JavaScript(部分瀏覽器),但考慮到JavaScript的依賴關(guān)系,他們的執(zhí)行依然是按照引入順序進(jìn)行的。2011-03-03