在很多 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();
// 创建 canvas,并设置尺寸
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);
// 将 canvas 转换为图片 DataURL
const imgData = canvas.toDataURL('image/png');
document.getElementById('resultImg').src = imgData;
});
</script>
</body>
</html>

代码解析:

  1. 创建 canvas:根据目标元素 #capture 的尺寸创建 canvas,确保捕获的图片与实际尺寸一致。

  2. 递归遍历 DOMrenderNode 函数递归遍历目标元素的所有子节点,对每个元素获取其 getBoundingClientRect() 数据,并调用 getComputedStyle 获取样式。

  3. 绘制背景与文本

    • 对于元素节点,若有背景颜色,则在 canvas 上填充对应区域。

    • 对于文本节点,简化处理,将文本用父元素的样式绘制在相应位置(实际应用中需要更精确的定位计算)。

  4. 输出图片:最后,将 canvas 内容转换为 DataURL,并设置到 <img> 标签中展示。

使用 getDisplayMedia 实现屏幕截图

核心原理

浏览器提供了 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();

// 等待视频加载元数据后,设置 canvas 大小并捕获一帧
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);

// 将 canvas 转换为图片数据 URL,并设置到 img 标签上显示
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 状态反馈。

使用开源工具包

一般在生产环境中,需要前端对网页进行截图操作的时候,都会使用成熟的开源工具的方案,接下来介绍两个市面上比较常用的两个工具 HTML2Canvasdom-to-image

HTML2Canvas

HTML2Canvas 是一个流行的 JavaScript 库,它通过遍历 DOM,将页面上的内容”拍照”并转换为 canvas 元素,进而生成图片数据(Base64 或 Blob 格式)。
优点:

  • 易于集成,使用简单

  • 支持大部分现代浏览器

缺点:

  • 对复杂的 CSS 样式和跨域图片可能存在限制

  • 截图精度可能受限于浏览器渲染

工具的安装使用代码示例

1
2
3
4
npm install html2canvas --save

# 或HTML 中 CDN 引入
<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 => {
// 将 canvas 转换为 DataURL
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

# HTML CDN 引入
<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!