dom-to-image、html-to-image、html2canvas 和 screenshot-desktop 都是用于将视觉内容转换为图像的工具,但适用场景截然不同。前三者专注于在浏览器环境中将 DOM 元素渲染为 PNG、JPEG 或 SVG 图像,常用于生成分享卡片、导出报表或保存可视化结果;而 screenshot-desktop 是一个 Node.js 原生模块,用于截取操作系统级别的桌面屏幕,适用于 Electron 应用、自动化测试或后端服务。这些工具在渲染原理、环境支持、功能特性和维护状态上存在显著差异,开发者需根据具体需求谨慎选择。
在现代前端开发中,将网页内容转换为图像(如 PNG 或 JPEG)是常见的需求 —— 无论是生成分享卡片、导出报表,还是保存可视化结果。dom-to-image、html-to-image、html2canvas 和 screenshot-desktop 都提供了这类能力,但它们的实现方式、适用场景和限制差异显著。本文从工程实践角度深入剖析这四个库的技术细节。
前三者(dom-to-image、html-to-image、html2canvas)均基于 浏览器环境,通过解析 DOM 并使用 <canvas> 重绘页面内容来生成图像。而 screenshot-desktop 完全不同 —— 它是一个 Node.js 原生模块,依赖操作系统 API 截取整个桌面或指定窗口,无法在浏览器中运行。
// html2canvas: 浏览器内渲染 DOM 到 canvas
import html2canvas from 'html2canvas';
const element = document.getElementById('capture');
html2canvas(element).then(canvas => {
const imgData = canvas.toDataURL('image/png');
// 在浏览器中使用 imgData
});
// screenshot-desktop: Node.js 中截取系统屏幕
const screenshot = require('screenshot-desktop');
// 仅能在 Node.js 环境中运行
screenshot({ filename: 'screenshot.png' })
.then(imgPath => console.log('Saved to', imgPath))
.catch(err => console.error(err));
⚠️ 注意:
screenshot-desktop不能用于网页应用中的用户操作截图,它适用于 Electron、自动化测试脚本或后端服务等需要访问本地屏幕的场景。
html2canvas:最成熟的 Canvas 渲染器html2canvas 是历史最久、功能最全面的浏览器端 DOM 转图像库。它尝试尽可能准确地重建 CSS 样式(包括 transform、box-shadow、border-radius 等),并支持跨域图片(通过代理配置)。
// html2canvas 支持复杂的 CSS 属性
html2canvas(document.body, {
useCORS: true, // 加载跨域图片
allowTaint: false, // 避免污染 canvas
backgroundColor: '#fff'
}).then(canvas => {
// 处理 canvas
});
但它对某些现代 CSS 特性(如 clip-path、CSS Grid 的复杂布局)支持有限,且无法处理 <iframe> 或 <video> 等非静态内容。
dom-to-image 与 html-to-image:SVG 中转方案这两个库采用不同的策略:先将 DOM 转换为 SVG 字符串,再将 SVG 渲染到 <canvas> 上。这种方式能更好地保留矢量图形和文本清晰度。
// dom-to-image
import domtoimage from 'dom-to-image';
domtoimage.toPng(document.getElementById('my-node'))
.then(dataUrl => {
// dataUrl 是 base64 PNG
});
// html-to-image
import { toPng } from 'html-to-image';
toPng(document.getElementById('my-node'))
.then(dataUrl => {
// 同样返回 base64 PNG
});
值得注意的是,html-to-image 实际上是 dom-to-image 的一个活跃维护分支。根据 GitHub 仓库说明,dom-to-image 已不再积极维护,而 html-to-image 修复了多个 bug 并增加了新功能(如更好的字体处理和错误边界)。
所有基于 <canvas> 的方案都受 同源策略 和 污染检测 限制。一旦 canvas 被跨域资源“污染”,就无法调用 toDataURL()。
html2canvas 提供 useCORS: true 选项,自动为 <img> 添加 crossorigin="anonymous" 并重试加载。html-to-image 也支持类似机制,通过 cacheBust: true 和 skipFonts: false 控制资源加载行为。// html-to-image 处理跨域图片
toPng(node, {
cacheBust: true,
skipFonts: false
});
而 screenshot-desktop 不涉及浏览器安全模型,因此无此限制 —— 但它也无法访问网页 DOM,只能截取像素级屏幕内容。
| 功能 | dom-to-image | html-to-image | html2canvas | screenshot-desktop |
|---|---|---|---|---|
| 浏览器环境 | ✅ | ✅ | ✅ | ❌ |
| Node.js 环境 | ❌ | ❌ | ❌(需 JSDOM 模拟) | ✅ |
| 支持 SVG 输出 | ✅ | ✅ | ❌ | ❌ |
| 支持 JPEG/PNG | ✅ | ✅ | ✅ | ✅(PNG/JPEG) |
| 自定义画布尺寸 | ✅ | ✅ | ✅ | ✅(全屏/区域) |
| 字体处理 | 基础 | 改进(支持 Web Fonts) | 较好 | 无(系统级截图) |
| 维护状态 | ⚠️ 停滞 | ✅ 活跃 | ✅ 活跃 | ✅ 活跃 |
html-to-imagehtml2canvas。import { toJpeg } from 'html-to-image';
toJpeg(document.querySelector('.share-card'), {
quality: 0.95,
pixelRatio: 2 // 提升分辨率
}).then(dataUrl => {
// 用于 <img src={dataUrl} />
});
html2canvashtml2canvas(chartContainer, {
scale: 2,
logging: false,
useCORS: true
}).then(canvas => {
// 导出高分辨率图表
});
screenshot-desktop// 在 Electron 主进程中
const screenshot = require('screenshot-desktop');
screenshot.listDisplays().then(displays => {
// 截取主显示器
return screenshot({ screen: displays[0].id });
});
dom-to-image 且无重大问题,可继续使用;但新项目应优先考虑 html-to-image。<video> 当前帧、<canvas> 动态内容(除非手动绘制)、WebGL 内容、伪元素(部分支持)。html-to-image 提供 fontEmbedCss 选项缓解此问题。html-to-image(新项目)或 html2canvas(复杂布局)。screenshot-desktop(仅限 Node.js)。dom-to-image:官方仓库已归档,存在未修复 bug。最终,没有“最好”的库 —— 只有“最适合当前场景”的工具。理解它们的底层机制,才能在架构设计时做出正确权衡。
不建议在新项目中使用 dom-to-image。该库已停止积极维护,存在已知的字体渲染和跨域图片问题,且其功能已被更活跃的 html-to-image 所覆盖。仅在维护遗留系统且无迁移计划时考虑继续使用。
选择 html-to-image 如果你需要在浏览器中将 DOM 转换为高质量图像,尤其重视文本清晰度、SVG 输出支持和现代 CSS 兼容性。它是 dom-to-image 的活跃继任者,修复了多项关键 bug,并提供了更好的 Web Font 处理和错误边界,适合生成分享图、证书或简单 UI 快照。
选择 html2canvas 如果你的页面包含复杂的 CSS 布局(如 transform、flexbox、box-shadow)或需要更精确的样式还原。它对动态内容的支持相对成熟,适合仪表盘、数据可视化图表等场景,但需注意其对某些现代 CSS 特性(如 clip-path)的支持仍有限。
选择 screenshot-desktop 仅当你在 Node.js 环境(如 Electron 应用、自动化脚本或后端服务)中需要截取操作系统级别的屏幕内容。它无法访问网页 DOM,而是直接调用系统 API 获取像素数据,因此完全不适用于标准 Web 应用中的用户交互截图需求。
dom-to-image is a library which can turn arbitrary DOM node into a vector (SVG) or raster (PNG or JPEG) image, written in JavaScript. It's based on domvas by Paul Bakaus and has been completely rewritten, with some bugs fixed and some new features (like web font and image support) added.
npm install dom-to-image
Then load
/* in ES 6 */
import domtoimage from 'dom-to-image';
/* in ES 5 */
var domtoimage = require('dom-to-image');
bower install dom-to-image
Include either src/dom-to-image.js or dist/dom-to-image.min.js in your page
and it will make the domtoimage variable available in the global scope.
<script src="path/to/dom-to-image.min.js" />
<script>
domtoimage.toPng(node)
//...
</script>
All the top level functions accept DOM node and rendering options,
and return promises, which are fulfilled with corresponding data URLs.
Get a PNG image base64-encoded data URL and display right away:
var node = document.getElementById('my-node');
domtoimage.toPng(node)
.then(function (dataUrl) {
var img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
})
.catch(function (error) {
console.error('oops, something went wrong!', error);
});
Get a PNG image blob and download it (using FileSaver, for example):
domtoimage.toBlob(document.getElementById('my-node'))
.then(function (blob) {
window.saveAs(blob, 'my-node.png');
});
Save and download a compressed JPEG image:
domtoimage.toJpeg(document.getElementById('my-node'), { quality: 0.95 })
.then(function (dataUrl) {
var link = document.createElement('a');
link.download = 'my-image-name.jpeg';
link.href = dataUrl;
link.click();
});
Get an SVG data URL, but filter out all the <i> elements:
function filter (node) {
return (node.tagName !== 'i');
}
domtoimage.toSvg(document.getElementById('my-node'), {filter: filter})
.then(function (dataUrl) {
/* do something */
});
Get the raw pixel data as a Uint8Array with every 4 array elements representing the RGBA data of a pixel:
var node = document.getElementById('my-node');
domtoimage.toPixelData(node)
.then(function (pixels) {
for (var y = 0; y < node.scrollHeight; ++y) {
for (var x = 0; x < node.scrollWidth; ++x) {
pixelAtXYOffset = (4 * y * node.scrollHeight) + (4 * x);
/* pixelAtXY is a Uint8Array[4] containing RGBA values of the pixel at (x, y) in the range 0..255 */
pixelAtXY = pixels.slice(pixelAtXYOffset, pixelAtXYOffset + 4);
}
}
});
All the functions under impl are not public API and are exposed only
for unit testing.
A function taking DOM node as argument. Should return true if passed node should be included in the output (excluding node means excluding it's children as well). Not called on the root node.
A string value for the background color, any valid CSS color value.
Height and width in pixels to be applied to node before rendering.
An object whose properties to be copied to node's style before rendering. You might want to check this reference for JavaScript names of CSS properties.
A number between 0 and 1 indicating image quality (e.g. 0.92 => 92%) of the JPEG image. Defaults to 1.0 (100%)
Set to true to append the current time as a query string to URL requests to enable cache busting. Defaults to false
A data URL for a placeholder image that will be used when fetching an image fails. Defaults to undefined and will throw an error on failed images
It's tested on latest Chrome and Firefox (49 and 45 respectively at the time
of writing), with Chrome performing significantly better on big DOM trees,
possibly due to it's more performant SVG support, and the fact that it supports
CSSStyleDeclaration.cssText property.
Internet Explorer is not (and will not be) supported, as it does not support
SVG <foreignObject> tag
Safari is not supported, as it uses a stricter security model on <foreignObject> tag. Suggested workaround is to use toSvg and render on the server.`
Only standard lib is currently used, but make sure your browser supports:
<foreignObject> tagMost importantly, tests depend on:
js-imagediff, to compare rendered and control images
ocrad.js, for the parts when you can't compare images (due to the browser rendering differences) and just have to test whether the text is rendered
There might some day exist (or maybe already exists?) a simple and standard way of exporting parts of the HTML to image (and then this script can only serve as an evidence of all the hoops I had to jump through in order to get such obvious thing done) but I haven't found one so far.
This library uses a feature of SVG that allows having arbitrary HTML content
inside of the <foreignObject> tag. So, in order to render that DOM node
for you, following steps are taken:
Clone the original DOM node recursively
Compute the style for the node and each sub-node and copy it to corresponding clone
Embed web fonts
find all the @font-face declarations that might represent web fonts
parse file URLs, download corresponding files
base64-encode and inline content as data: URLs
concatenate all the processed CSS rules and put them into one <style>
element, then attach it to the clone
Embed images
embed image URLs in <img> elements
inline images used in background CSS property, in a fashion similar to
fonts
Serialize the cloned node to XML
Wrap XML into the <foreignObject> tag, then into the SVG, then make it a
data URL
Optionally, to get PNG content or raw pixel data as a Uint8Array, create an Image element with the SVG as a source, and render it on an off-screen canvas, that you have also created, then read the content from the canvas
Done!
if the DOM node you want to render includes a <canvas> element with
something drawn on it, it should be handled fine, unless the canvas is
tainted -
in this case rendering will rather not succeed.
at the time of writing, Firefox has a problem with some external stylesheets (see issue #13). In such case, the error will be caught and logged.
Anatolii Saienko, Paul Bakaus (original idea)
MIT