最近准备做一个新的项目,其中有一个分享功能希望能够把 HTML 页面以图片的方式发布到社交平台。所以研究了一下 HTML 转图片的功能,实现这个功能需要用到 SVG 和 Canvas 技术。
先来说说大致流程,就跟「把大象关进冰箱」一样,需要三步:
- 首先,确定需要渲染的 HTML 元素和 CSS 样式,CSS 样式最好能独立出来。
- 使用 SVG 元素包裹需要渲染的 HTML 代码。
- 使用 Canvas 把 SVG 代码转换为 PNG 格式图片。
我将从一段简单的 HTML 代码开始,来详细介绍一下这三步的流程。
首先来看一段简单的 HTML 代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>HTML to Image</title>
<style>
.card {
background-color: white;
margin: 2em;
display: flex;
box-shadow: 0 0 1em rgba(0, 0, 0, 0.2);
border-radius: 0.5em;
width: 400px;
}
.cover {
width: 180px;
}
.cover img {
display: block;
max-width: 100%;
border-radius: 0.5em 0 0 0.5em;
}
.text {
flex: 1;
text-align: center;
padding-top: 2em;
}
.text h1 {
font-size: 1em;
}
</style>
</head>
<body>
<h2>HTML</h2>
<div class="card">
<div class="cover">
<img src="/libai.jpg" />
</div>
<div class="text">
<h1>静夜思</h1>
<p>床前看月光</p>
<p>疑是地上霜</p>
<p>抬头望山月</p>
<p>低头思故乡</p>
</div>
</div>
</body>
</html>
引用了李白的一首诗,这段 HTML 和 CSS 代码的显示效果如下:
假设这段代码中 class
属性为 card
的标签就是要转换问图片的内容。先看看如何把它包裹到 SVG 中并显示。把 body
标签中的内容替换为如下代码:
<h2>SVG</h2>
<svg width="470" height="320" viewBox="0 0 470 320">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
<div class="card">
<div class="cover">
<img src="/libai.jpg" />
</div>
<div class="text">
<h1>静夜思</h1>
<p>床前看月光</p>
<p>疑是地上霜</p>
<p>抬头望山月</p>
<p>低头思故乡</p>
</div>
</div>
</div>
</foreignObject>
</svg>
这段代码主要就是 foreignObject
标签和它的 xmlns
命名空间属性。定义为 HTML 的命名空间后,就可以使 SVG 标签以 HTML 的方式渲染其中的内容。来看看效果:
是不是和 HTML 效果一模一样?
SVG 是支持直接在 img
标签中显示的图片格式。有了这个 SVG 的渲染内容,接下来就只需要通过 Canvas 图形绘制技术来渲染 SVG 图片并转换格式就行了。
在 svg
标签后添加以下代码:
<button>生成图片</button>
<script>
// 把图片资源转换为 Data URI 格式
const buildImageData = (resource) => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = resource.width
canvas.height = resource.height
context.drawImage(resource, 0, 0)
return canvas.toDataURL()
}
// 点击按钮时生成图片并下载
document.querySelector('button').addEventListener('click', () => {
const img = new Image()
const svg = document.querySelector('svg').cloneNode(true)
// 把 style 标签中的 CSS 代码引入 SVG 图片中
svg.querySelector('foreignObject')
.before(document.querySelector('style')
.cloneNode(true))
// 转换 HTML 代码中的图片地址
svgImg = svg.querySelector('img')
svgImg.src = buildImageData(svgImg)
// 设置 HTML 转 SVG 后的图片内容格式
img.src = 'data:image/svg+xml;charset=utf-8,'+
new XMLSerializer().serializeToString(svg)
// 延时 100 毫秒,等待 img 对象完成图片的渲染加载后执行下载
setTimeout(() => {
const a = document.createElement('a')
a.download = 'image.png'
a.href = buildImageData(img)
a.click()
}, 100)
})
</script>
代码中关键位置添加了有注释,这里就不再重复解释。主要有两个关键点:
- 必须把和导出 HTML 相关的 CSS 样式都塞到
svg
标签中去,否则生成的图片会没有样式。 - 如果要导出的 HTML 中包含 URL 地址的
img
标签,需要转换为使用 Data URI 格式,否则会出错。
点击「生成图片」按钮,就会出现一个下载提示。下载内容是 HTML 转换成的 PNG 图片。这是上面 HTML 内容转换成图片的结果: