用JavaScript实现浏览器截图功能的全过程


最近项目中要实现一个需求:
 

用户希望在上传文件时能够直接截图上传,并且要求能够截图浏览器外的内容。

多方查找之下找到了类似的解决方案,但个人感觉操作上有些抽象,仅供参考。

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">确认</button>
               <button id="cancel-screenshot">取消</button>
           </div>
    </div>
    

CSS部分

  • 根据需求自行调整就好
     #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'); // 获取的图片展示区
    const canvas = document.getElementById('screenshot-canvas'); // 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'); // 取消按钮
    
  • 变量定义

    let isCapturing = false;
    let isSelecting = false;
    let startX = 0;
    let startY = 0;
    let endX = 0;
    let endY = 0;
    let screenCapture = null;
    
  • 设置画布大小

    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];
       
       // 创建视频元素以捕获屏幕
       const videoElem = document.createElement('video');
       videoElem.srcObject = screenCapture;
       
       // 当视频加载完成后,绘制到画布上
       videoElem.onloadedmetadata = () =&gt; {
           videoElem.play();
           
           // 设置画布大小
           setCanvasSize();
           
           // 绘制视频帧到画布
           ctx.drawImage(videoElem, 0, 0, canvas.width, canvas.height);
           
           // 停止视频轨道
           videoTrack.stop();
           
           // 显示截图容器
           screenshotContainer.style.display = 'block';
           isCapturing = true;
       };
    

    } catch (err) {
    console.error(‘截图失败:’, err);
    alert(‘截图失败,请确保您已授予屏幕捕获权限。’);
    }
    }

    这里会请求屏幕捕获权限并获取屏幕内容,这里可以选择 浏览器标签页windows打开的窗口整个屏幕,确实可以获取到浏览器之外的内容。

  • 更新选择区域

    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';
    

    }

  • 确认截图(在这里获取截图结果)

    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);
    
    // 创建新画布以保存选定区域
    const resultCanvas = document.createElement('canvas');
    resultCanvas.width = width;
    resultCanvas.height = height;
    const resultCtx = resultCanvas.getContext('2d');
    
    // 将选定区域绘制到新画布
    resultCtx.drawImage(
        canvas,
        left, top, width, height,
        0, 0, width, height
    );
    
    // 在这里获取截图结果
    // 如果想生成成一个Base64url
    const base64Url = resultCanvas.toDataURL();
    
    // 如果想生成成一个File对象
    const resultFile = dataURLtoFile(resultCanvas.toDataURL(), "截图.png")
    
    // 重置截图状态
    resetScreenshot();
    

    }

  • 将Base64数据转换为File对象(不需要转换结果为文件对象可以不写这段)

    function dataURLtoFile(dataurl, filename) {
        // 将Base64数据拆分为MIME类型和实际数据
        const arr = dataurl.split(',');
        const mime = arr[0].match(/:(.*?);/)[1]; // 获取MIME类型
        const bstr = atob(arr[1]); // 解码Base64数据
        let n = bstr.length;
        const u8arr = new Uint8Array(n);
    
    
    // 将解码后的数据转换为Uint8Array
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    
    // 创建并返回File对象
    return new File([u8arr], filename, { type: mime });
    

    }

  • 取消截图

    function cancelScreenshot() {
        resetScreenshot();
    }
    
  • 重置截图状态

    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);
    }
    
  • 各类监听事件

    // 事件监听器
    startButton.addEventListener('click', startScreenshot);
    confirmButton.addEventListener('click', confirmScreenshot);
    cancelButton.addEventListener('click', cancelScreenshot);
    
    

    // 鼠标事件处理
    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;
    });

    // 窗口大小改变时重新设置画布大小
    window.addEventListener(‘resize’, function() {
    if (isCapturing) {
    setCanvasSize();
    }
    });

  • 键盘快捷键(不需要可以不用)

    // 键盘快捷键
    document.addEventListener('keydown', function(e) {
        // Alt + A 开始截图
        if (e.altKey && e.key === 'a') {
            e.preventDefault();
            startScreenshot();
        }
        
    
    // Enter 确认截图
    if (e.key === 'Enter' &amp;&amp; isCapturing) {
        confirmScreenshot();
    }
    
    // Esc 取消截图
    if (e.key === 'Escape' &amp;&amp; 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;
    }
&lt;/style&gt;

</head>
<body>
<div class=”container”>
<button id=”start-screenshot” class=”button”>开始截图</button>
</div>

&lt;div id="screenshot-container"&gt;
    &lt;canvas id="screenshot-canvas"&gt;&lt;/canvas&gt;
    &lt;div class="selection-area" id="selection-area" style="display: none;"&gt;&lt;/div&gt;
    &lt;div class="toolbar" id="screenshot-toolbar" style="display: none;"&gt;
        &lt;button id="confirm-screenshot"&gt;确认&lt;/button&gt;
        &lt;button id="cancel-screenshot"&gt;取消&lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
    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');

        // 截图状态
        let isCapturing = false;
        let isSelecting = false;
        let startX = 0;
        let startY = 0;
        let endX = 0;
        let endY = 0;
        let screenCapture = null;

        // 设置画布大小
        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];
                
                // 创建视频元素以捕获屏幕
                const videoElem = document.createElement('video');
                videoElem.srcObject = screenCapture;
                
                // 当视频加载完成后,绘制到画布上
                videoElem.onloadedmetadata = () =&gt; {
                    videoElem.play();
                    
                    // 设置画布大小
                    setCanvasSize();
                    
                    // 绘制视频帧到画布
                    ctx.drawImage(videoElem, 0, 0, canvas.width, canvas.height);
                    
                    // 停止视频轨道
                    videoTrack.stop();
                    
                    // 显示截图容器
                    screenshotContainer.style.display = 'block';
                    isCapturing = true;
                };
            } catch (err) {
                console.error('截图失败:', err);
                alert('截图失败,请确保您已授予屏幕捕获权限。');
            }
        }

        // 更新选择区域
        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数据转换为File对象
        function dataURLtoFile(dataurl, filename) {
            // 将Base64数据拆分为MIME类型和实际数据
            const arr = dataurl.split(',');
            const mime = arr[0].match(/:(.*?);/)[1]; // 获取MIME类型
            const bstr = atob(arr[1]); // 解码Base64数据
            let n = bstr.length;
            const u8arr = new Uint8Array(n);

            // 将解码后的数据转换为Uint8Array
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }

            // 创建并返回File对象
            return new File([u8arr], filename, { type: mime });
        }

        // 确认截图
        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);

            // 创建新画布以保存选定区域
            const resultCanvas = document.createElement('canvas');
            resultCanvas.width = width;
            resultCanvas.height = height;
            const resultCtx = resultCanvas.getContext('2d');

            // 将选定区域绘制到新画布
            resultCtx.drawImage(
                canvas,
                left, top, width, height,
                0, 0, width, height
            );

            // 在这里获取截图结果
            // 如果想生成成一个Base64url
            const base64Url = resultCanvas.toDataURL();
            
            // 如果想生成成一个File对象
            const resultFile = dataURLtoFile(resultCanvas.toDataURL(), "截图.png")

            // 重置截图状态
            resetScreenshot();
        }

        // 取消截图
        function cancelScreenshot() {
            resetScreenshot();
        }

        // 重置截图状态
        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);
        }

        // 事件监听器
        startButton.addEventListener('click', startScreenshot);
        confirmButton.addEventListener('click', confirmScreenshot);
        cancelButton.addEventListener('click', cancelScreenshot);

        // 鼠标事件处理
        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 &amp;&amp; e.key === 'a') {
                e.preventDefault();
                startScreenshot();
            }
            
            // Enter 确认截图
            if (e.key === 'Enter' &amp;&amp; isCapturing) {
                confirmScreenshot();
            }
            
            // Esc 取消截图
            if (e.key === 'Escape' &amp;&amp; isCapturing) {
                cancelScreenshot();
            }
        });

        // 窗口大小改变时重新设置画布大小
        window.addEventListener('resize', function() {
            if (isCapturing) {
                setCanvasSize();
            }
        });
    });
&lt;/script&gt;

</body>
</html>