JavaScript实现网页截图导出方案

前言

网页截图导出不是一个非常高频的需求,但时不时的也会遇到。这里总结一下系统的解决方案,然后从中选择合适自己的。

截图导出可以看到是两个功能,第一步实现截图,第二步实现导出也就是下载能力。

截图实现

首先,我们必须明白正常javascript是运行在浏览器里的,本身没有截图的能力。所以要想实现截图,必须通过其他迂回方案实现,废话少说,直接上结论。

前端方案1 canvas

实现原理:
html2canvas 是一个 HTML 渲染器,屏幕截图是基于 DOM,因此生成的图片并不一定 100% 一致,因为它没有制作实际的屏幕截图,而是根据页面上可用的信息构建屏幕截图。
文档介绍的比较清楚,canvas只是去还原dom的展示效果。

根据实现原理,可以想象,实现成本还是比较高的,需要解析dom和css样式,而且css样式不一定能完美映射到canvas,另外还会受限于canvas收到的一些限制,比如跨域资源问题。

前端方案2 svg

实现原理:

核心要素是SVG 的一个特性,允许在 <foreignObject> 标签中包含任意的 HTML 内容。所以为了渲染该dom节点,需要如下步骤:

  1. 递归克隆原始DOM节点
  2. 计算节点和每个子节点的样式并将其复制到相应的克隆,并且要重新创建伪元素,因为它们当然不会以任何方式克隆
  3. 嵌入网页字体,链接所有css样式到style标签,应用到clone节点
  4. 嵌入图像
    在 <img> 元素中嵌入图像 URL
    背景 CSS 属性中使用的内嵌图像,以类似于字体的方式
  5. 将克隆的节点序列化为 XML
  6. 将 XML 包裹到 <foreignObject> 标签中,然后包裹到 SVG 中,然后使其成为数据 URL
  7. 创建一个以 SVG 作为源的 Image 元素,并将其呈现在您也已创建的离屏画布上,然后从画布中读取内容

嗯,这就是svg方式实现了,和canvas方式一样,需要我们处理dom,css和资源,但是后续的绘制渲染工作交给了浏览器,所以减轻了很多工作量和代码量。

服务端方案

 

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。这里具体就不介绍了,我们只使用它的截图功能。

这个就是真正的截图能力了,官网有demo。

考虑到业务层的页面都需要用户访问权限,所以正确的步骤应该是:

  1. 启动一个node服务,利用puppeter实现截图能力
  2. 客户端发起一个请求,携带cookie
  3. 第一步启动的服务处理此请求,生成图片,地址返回给客户端
  4. 客户端拿到图片资源,进行下载即可

对比

html2canvas

优点

简单页面截图效果还可以,兼容性还好

缺点

  • 部分样式无法支持
  • 复杂页面,惨不忍睹。
  • cors可以解决跨域,但是实际应用还是问题百出
  • 实现非常复杂,难以改动

在我们项目几乎无法使用

dom-to-image

优点

  • 实现简单,可下载后自行改动源码
  • 还原度良好,尤其chrome支持非常好

缺点

  • safari支持不是很好,不过一些问题可以解决(canvas节点会导出空白,如果使用绘图引擎的话,建议转化成svg引擎绘图)
  • ie,edge 无法使用

基本能支持业务需求

后端截图

优点

  • 真实截图,终极方案,无兼容问题

缺点

  • 实现流程稍微繁琐
  • 需要考虑 用户权限认证问题

导出实现

导出也就是下载文件到本地

a标签

原理是通过a标签的download属性实现下载。

// 基本使用
<a href="/images/xxxxx.jpg" download="filename">

// 脚本触发
const download = (filename, url) => {
    let a = document.createElement('a'); 
    a.style = 'display: none'; // 创建一个隐藏的a标签
    a.download = filename;
    a.href = url;
    document.body.appendChild(a);
    a.click(); // 触发a标签的click事件
    document.body.removeChild(a);
}

blob文件流对象

这个需要服务端支持,发起请求,服务端返回数据流,然后前端触发下载。
具体就不多说了,网上有很多文章。

这里推荐几个封装好的下载文件库
downloadjs

file-saver
THE END