在工作时,需要实现一个功能:把一个HTML网页的转换为图像。我想到的第一个想法是使用第三方库,但像dom-to-image
或使用Chrome Headless
,如Puppeteer
。那如何使用纯Javascript
解决这种需求呢?
让我们尝试在不使用任何库的情况下实现这一点。
使用Canvas将HTML网页转换为图像
由于安全原因,我们不能直接将HTML绘制到Canvas中。我们将采用另一种更安全的方法。
- 创建包含渲染内容的SVG图像
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
</svg>
- 在SVG中插入一个
<foreignObject>
元素,它将包含HTML
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">
</foreignObject>
</svg>
- 在
<foreignObject>
节点内添加XHTML内容
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">.
<style>em{color:red;}</style>
Hey there...
</div>
</foreignObject>
</svg>
- 创建 img 对象,并将 img 的
src
属性设置为该图像的data url
:
const tempImg = document.createElement('img')
tempImg.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml">Hey there...</div></foreignObject></svg>')
- 将此图像绘制到画布上,并设置画布为img 对象的
src
属性值:
const newImg = document.createElement('img')
newImg.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml">Hey there...</div></foreignObject></svg>')
// 对图像添加事件监听
newImg.addEventListener('load', onNewImageLoad)
// 将图像绘制到画布并设置img.src
function onNewImageLoad(e){
ctx.drawImage(e.target, 0, 0)
targetImg.src = canvas.toDataURL()
}
完整代码
<html>
<head>
<meta charset="UTF-8" />
<style>
body {
display: flex;
flex-flow: column wrap;
align-items: center;
justify-content: flex-start;
background-image: linear-gradient(0, rgb(169, 144, 177), rgb(205, 169, 223));
}
h1 {
margin-top: 5vh;
margin-bottom: 1vh;
color: white;
}
p {
margin: 0;
color: white;
}
</style>
</head>
<body>
<h1>Convert HTML into Image!<h1 />
<script>
const { body } = document;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = canvas.height = 100;
const newImg = document.createElement("img");
newImg.addEventListener("load", onNewImageLoad);
newImg.src =
"data:image/svg+xml," +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml"> Hey there...</div></foreignObject></svg>'
);
const targetImg = document.createElement("img");
body.appendChild(targetImg);
function onNewImageLoad(e) {
ctx.drawImage(e.target, 0, 0);
targetImg.src = canvas.toDataURL();
}
</script>
</body>
</html>
为什么使用SVG和Canvas是安全的?
SVG图像的实现有很大的限制,因为我们不允许SVG
图像加载外部资源,即使是出现在同一个域上的资源。不允许在SVG图像中编写脚本,无法从其他脚本访问SVG图像的DOM
, SVG图像中的DOM
元素不能接收输入事件。因此,无法将特权信息加载到表单控件中(例如<input type="file">
中的完整路径)并呈现它。
从安全性的角度来看,脚本不能直接接触渲染到画布的DOM节点,这一限制非常重要。