简介
剖析流行的截图插件 html2canvas 的实现方案,探索其功能上的一些不足之处及不能正确截取的一些场景,比如不支持 CSS 的 box-shadow 截取情况等。探索一种新的实现方式,能够避免多数目前 html2canvas 不支持的情况,解密其原理,深究 Canvas 绘图的机制。
本篇文章你可以学到:
- 纯前端网页截图的基本原理
- html2canvas 的核心原理
- SVG 内嵌 HTML 的方式
- Canvas 渲染 SVG 的方式及各种问题的解决方案
适合人群:前端开发
开篇
平时很多时候,需要把当前页面或者页面某一部分内容保存为图片分享出去,也或者有其他的业务用途,这种在很多的营销场景和裂变的过程都会使用到,那我们要把一个页面的内容转化为图片的这个过程,就是比较需要探讨的了。
首先这种情况,想到的实现方案就是使用 Canvas 来实现,我们探索一下基本实现步骤:
- 把需要分享或者记录的内容绘制到 Canvas 上
- 把绘制之后的 Canvas 转换为图片
这里需要明确的一点就是,只要把数据绘制到 Canvas 上,这就在 Canvas 画布上形成了被保存在内存中的像素点信息,所以可以直接调用 Canvas 的 API 方法 toDataURL、toBlob,把已经形成的像素信息转化为可以被访问的资源 URI,同时保存在服务器当中。这就很轻松的解决了第二步(把 Canvas 转为图片链接),下面是代码的实现:
在实现了第二步的情况之下,需要关注的就是第一步的内容,怎么把内容绘制到 Canvas 上,我们知道 Canvas 的绘图环境有一个方法是 ctx.drawImage,可以绘制部分元素到 Canvas 上,包含图片元素 Image、svg 元素、视频元素 Video、Canvas 元素、ImageBitmap 数据等,但是对于一般的其他 div 或者列表 li 元素它是不可以被绘制的。
所以,这不是直接调用绘图的 API 就可以办到的,我们就需要思考其他的方法。在一般的实现上,比较常见的就是使用 html2canvas,那么我们先来聊聊 html2canvas 的使用和实现。
html2canvas 的使用及实现
使用
首先看一下 html2cavas 的使用方法:
调用 html2canvas 方法传入想要截取的 Dom,执行之后,返回一个 Promise,接收到的 Canvas 上,就绘制了我们想要截取的 Dom 元素。到这一步之后,我们再调取 Canvas 转图片的方法,就可以对其做其他的处理。
这里它的 html2canvas 方法还支持第二个选项传入一些用户的配置参数,比如是否启用缓存、整个绘图 Canvas 的宽高值等。
在这个转换的过程,在 html2canvas 的内部,是怎么把 Dom 元素绘制到 Canvas 上的,这是咱们需要思考的问题!
实现
首先咱们先献上一个内部的大致流程图:
对比着内部的流程图,就可以理一下整体的思路,整体的思路就是遍历目标节点和子节点,收集样式,计算节点本身的层级关系和根据不同的优先级绘制到画布中,下面基于这个思路,咱们深入一下整个过程。
1. 调用 html2canvs 函数,直接返回一个执行函数,这一步没有什么。
2. 在执行函数的内部第一步是构建配置项 defaultOptions,在合并默认配置的过程中,有一个缓存的配置,它会生成处理缓存的方法。
- 处理缓存类,对于一个页面中的多个不同的地方渲染调用多次的情况做优化,避免同一个资源被多次加载;
- 缓存类里面控制了所有图片的加载和处理,包括使用 proxy 代理和使用 cors 跨域资源共享这两种情况资源的处理,同时也对 base64 和 blob 这两种形式资源的处理。比如如果渲染 Dom 里面包含一个图片的链接类型是 blob,使用的方式就是如下处理,然后添加到缓存类中,下次使用就不需要再重新请求。
3. 在上一步生成了默认配置的情况之下,传入需要绘制的目标节点 element 和配置到 DocumentCloner 里面,这个过程会克隆目标节点所在的文档节点 document,同时把目标节点也克隆出来。这个过程中,只是克隆了开发者定义的对应节点样式,并不是结合浏览器渲染生成特定视图最后的样式。
如上这个 .box 的元素节点,定义的样式只有高度,但是在浏览器渲染之下,会对它设置默认的文字样式等等。
4. 基于上一步的情况,就需要把克隆出来的目标节点所在的文档节点 document 进行一次浏览器的渲染,然后在收集最终目标节点的样式。于此,把克隆出来的目标节点的 document 装载到一个 iframe 里面,进行一次渲染,然后就可以获取到经过浏览器视图真实呈现的节点样式。
在这个过程中,就可以通过 window.getComputedStyle 这个 API 拿到要克隆的目标节点上所有的样式了(包含自定义和浏览器默认的结合最终的样式)。
5. 目标节点的样式和内容都获取到了之后,就需要把它所承载的数据信息转化为 Canvas 可以使用的数据类型,比如某一个子节点的宽度设置为 50%或者 2rem,在这个过程中,就需要根据父级的宽度把它计算成为像素级别的单位。同时对于每一个节点而言需要绘制的包括了边框、背景、阴影、内容,而对于内容就包含图片、文字、视频等。这个过程就需要对目标节点的所有属性进行解析构造,分析成为可以理解的数据形式。