在很多 Web 应用场景中,我们都需要实现截图功能,例如生成报告、保存页面状态、用户反馈或分享内容等。通常,市面上有 HTML2Canvas、dom-to-image 等工具,但如果我们希望不依赖这些第三方库,纯粹依靠浏览器原生 API 来实现截图的话该如何去实现呢? 本文将介绍两种使用原生 API 实现前端网页截图的的方法,并给出关键代码的演示,最后再介绍两种开源的截图工具的使用。
使用 Canvas 绘制 Dom 元素 核心原理 基本思路是通过解析 DOM 树,并使用 Canvas 2D API 将每个 DOM 元素(包括其样式、文本和图片)逐个绘制到 canvas 上,最终生成一张完整的截图。
遍历 DOM 树 :从目标元素开始递归遍历所有子元素。
获取计算样式 :利用 window.getComputedStyle 获取每个元素的最终样式(背景、字体、边框等)。
定位绘制 :根据元素的 getBoundingClientRect() 数据,计算相对于目标区域的位置。
使用 Canvas 绘制 :调用 Canvas 2D API,将每个元素绘制到 canvas 上,再通过 canvas.toDataURL() 生成图片。
代码示例 由于 HTML2Canvas 需要处理大量 CSS 特性和复杂布局,这里我们给出一个简化示例,只实现了对基本背景颜色和文本节点的支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <title > 原生截图示例</title > <style > #capture { width : 400px ; padding : 20px ; background : #f5f5f5 ; border : 1px solid #ccc ; } #capture h1 { color : #333 ; font-size : 24px ; margin : 0 ; } #capture p { color : #666 ; font-size : 16px ; } </style > </head > <body > <div id ="capture" > <h1 > 示例标题</h1 > <p > 这是一个简单的示例,用于演示如何原生实现截图。</p > </div > <button id ="btnCapture" > 截取 #capture</button > <br > <img id ="resultImg" alt ="截图结果" /> <script > function captureElement (element ) { const rect = element.getBoundingClientRect (); const canvas = document .createElement ('canvas' ); canvas.width = rect.width ; canvas.height = rect.height ; const ctx = canvas.getContext ('2d' ); function renderNode (node ) { if (node.nodeType === Node .ELEMENT_NODE ) { const style = window .getComputedStyle (node); const nodeRect = node.getBoundingClientRect (); const x = nodeRect.left - rect.left ; const y = nodeRect.top - rect.top ; const bgColor = style.backgroundColor ; if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent' ) { ctx.fillStyle = bgColor; ctx.fillRect (x, y, nodeRect.width , nodeRect.height ); } Array .from (node.childNodes ).forEach (child => renderNode (child)); } else if (node.nodeType === Node .TEXT_NODE ) { const text = node.textContent .trim (); if (text) { const parentStyle = window .getComputedStyle (node.parentNode ); const font = parentStyle.font ; const color = parentStyle.color ; const parentRect = node.parentNode .getBoundingClientRect (); const x = parentRect.left - rect.left ; const y = parentRect.top - rect.top + parseInt (parentStyle.fontSize , 10 ); ctx.fillStyle = color; ctx.font = font; ctx.fillText (text, x, y); } } } renderNode (element); return canvas; } document .getElementById ('btnCapture' ).addEventListener ('click' , function ( ) { const captureElementNode = document .getElementById ('capture' ); const canvas = captureElement (captureElementNode); const imgData = canvas.toDataURL ('image/png' ); document .getElementById ('resultImg' ).src = imgData; }); </script > </body > </html >
代码解析:
创建 canvas :根据目标元素 #capture 的尺寸创建 canvas,确保捕获的图片与实际尺寸一致。
递归遍历 DOM :renderNode 函数递归遍历目标元素的所有子节点,对每个元素获取其 getBoundingClientRect() 数据,并调用 getComputedStyle 获取样式。
绘制背景与文本 :
输出图片 :最后,将 canvas 内容转换为 DataURL,并设置到 <img> 标签中展示。
核心原理 浏览器提供了 navigator.mediaDevices.getDisplayMedia API,用于捕获用户屏幕、窗口或标签页的媒体流。通过该 API,我们可以请求用户授权选择要共享的屏幕或窗口,然后从返回的媒体流中提取视频帧,将其绘制到 canvas 上,再转换为图片数据。
实例代码 下面的示例展示了如何使用 getDisplayMedia 捕获屏幕,并将一帧图像绘制到 canvas 上,然后生成图片并展示给用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <!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 { font-family : sans-serif; text-align : center; padding : 20px ; } #resultImg { margin-top : 20px ; max-width : 100% ; border : 1px solid #ccc ; } </style > </head > <body > <h1 > 原生截图示例</h1 > <button id ="captureBtn" > 点击截屏</button > <br > <img id ="resultImg" alt ="截图结果" /> <script > const captureBtn = document .getElementById ('captureBtn' ); const resultImg = document .getElementById ('resultImg' ); captureBtn.addEventListener ('click' , async () => { try { const stream = await navigator.mediaDevices .getDisplayMedia ({ video : true }); const video = document .createElement ('video' ); video.srcObject = stream; await video.play (); video.onloadedmetadata = () => { const canvas = document .createElement ('canvas' ); canvas.width = video.videoWidth ; canvas.height = video.videoHeight ; const ctx = canvas.getContext ('2d' ); ctx.drawImage (video, 0 , 0 , canvas.width , canvas.height ); const imgData = canvas.toDataURL ('image/png' ); resultImg.src = imgData; stream.getTracks ().forEach (track => track.stop ()); }; } catch (error) { console .error ("截图失败:" , error); } }); </script > </body > </html >
代码解析:
getDisplayMedia 当点击按钮时,通过 navigator.mediaDevices.getDisplayMedia({ video: true }) 请求用户授权屏幕捕获,返回一个视频流(MediaStream)。
video 元素播放流 创建一个 video 元素,并将视频流设置为其 srcObject,启动视频播放以确保能捕获到有效帧。
canvas 绘制与截图 在 video.onloadedmetadata 事件中,根据视频尺寸创建一个 canvas,然后调用 ctx.drawImage(video, 0, 0) 将当前视频帧绘制到 canvas 上。最后,调用 canvas.toDataURL('image/png') 将 canvas 内容转换为 Base64 格式的图片数据。
释放资源 捕获截图后,通过停止视频流中所有轨道来释放资源。
注意事项
用户授权
getDisplayMedia 需要用户主动授权,并且用户可以选择整个屏幕、某个窗口或标签页,这意味着截图结果可能不是你期望的特定 DOM 区域,而是用户所选的视图。
捕获内容范围
由于该 API 捕获的是媒体流,如果需要针对某个具体 DOM 元素进行截图,必须让该元素单独展示(例如,在一个专门的页面或弹窗中),否则很难仅捕获页面的一部分。
跨域资源问题
如果页面中存在跨域图片或资源,可能需要服务器支持 CORS,否则捕获的图片可能会受到 tainting 影响,导致无法调用 canvas.toDataURL()。
性能开销
屏幕捕获和 canvas 绘制均有一定性能开销,建议仅在用户明确需要截图时调用,并适当提供 loading 状态反馈。
使用开源工具包 一般在生产环境中,需要前端对网页进行截图操作的时候,都会使用成熟的开源工具的方案,接下来介绍两个市面上比较常用的两个工具 HTML2Canvas 和 dom-to-image。
HTML2Canvas HTML2Canvas 是一个流行的 JavaScript 库,它通过遍历 DOM,将页面上的内容”拍照”并转换为 canvas 元素,进而生成图片数据(Base64 或 Blob 格式)。 优点:
缺点:
对复杂的 CSS 样式和跨域图片可能存在限制
截图精度可能受限于浏览器渲染
工具的安装使用代码示例
1 2 3 4 npm install html2canvas --save <script src="https://cdn.jsdelivr.net/npm/[email protected] /dist/html2canvas.min.js" ></script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!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 > #capture { padding : 20px ; background : #f5f5f5 ; border : 1px solid #ccc ; } </style > </head > <body > <div id ="capture" > <h1 > 这是需要截图的区域</h1 > <p > 这里包含一些文本和样式。</p > </div > <button id ="btnCapture" > 截图</button > <img id ="resultImage" alt ="截图结果" /> <script src ="https://cdn.jsdelivr.net/npm/[email protected] /dist/html2canvas.min.js" > </script > <script > document .getElementById ('btnCapture' ).addEventListener ('click' , function ( ) { const captureElement = document .getElementById ('capture' ); html2canvas (captureElement).then (canvas => { const imgData = canvas.toDataURL ('image/png' ); document .getElementById ('resultImage' ).src = imgData; }).catch (err => { console .error ('截图失败:' , err); }); }); </script > </body > </html >
dom-to-image dom-to-image 也是一个流行的库,它可以将 DOM 节点转换为图片(SVG、PNG、JPEG 等格式)。 优点:
支持多种图片格式
提供更多配置选项,可以定制输出效果
缺点:
对复杂样式和嵌入资源(例如跨域图片)可能需要额外配置
安装与使用代码示例
1 2 3 4 npm install dom-to-image --save <script src="https://cdn.jsdelivr.net/npm/[email protected] /src/dom-to-image.min.js" ></script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > dom-to-image 截图示例</title > <style > #capture { padding : 20px ; background : #eef ; border : 1px solid #99c ; } </style > </head > <body > <div id ="capture" > <h1 > 这是需要截图的区域</h1 > <p > 这里包含一些文本和样式。</p > </div > <button id ="btnCapture" > 截图</button > <img id ="resultImage" alt ="截图结果" /> <script src ="https://cdn.jsdelivr.net/npm/[email protected] /src/dom-to-image.min.js" > </script > <script > document .getElementById ('btnCapture' ).addEventListener ('click' , function ( ) { const node = document .getElementById ('capture' ); domtoimage.toPng (node) .then (function (dataUrl ) { const img = document .getElementById ('resultImage' ); img.src = dataUrl; }) .catch (function (error ) { console .error ('截图失败!' , error); }); }); </script > </body > </html >
希望这篇文章能帮到你!
Happy Coding!