3

javascript实现网页截图导出方案

 2 years ago
source link: https://segmentfault.com/a/1190000040849728
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

前端方案1 canvas

代表库html2canvas
也有中文文档

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

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

前端方案2 svg

代表库dom-to-image

实现原理:
核心要素是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实现
Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。这里具体就不介绍了,我们只使用它的截图功能。

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

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

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

html2canvas

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

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

在我们项目几乎无法使用

dom-to-image

  • 实现简单,可下载后自行改动源码
  • 还原度良好,尤其chrome支持非常好
  • safari支持不是很好,不过一些问题可以解决

基本能支持业务需求

  • 真实截图,终极方案,无兼容问题
  • 实现流程稍微繁琐
  • 需要考虑 用户权限认证问题

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

原理是通过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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK