首页 > 技术文章 > html2canvas实现浏览器截图的原理(包含源码分析的通用方法)

kagol 2020-12-20 17:15 原文

DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。

官方网站:devui.design

Ng组件库:ng-devui(欢迎Star)

官方交流:添加DevUI小助手(devui-official)

DevUIHelper插件:DevUIHelper-LSP(欢迎Star)

 

引言

有时用户希望将我们的报表页面分享到其他的渠道,比如邮件、PPT等,每次都需要自己截图,一是很麻烦,二是截出来的图大小不一。

有没有办法在页面提供一个下载报表页面的功能,用户只需要点击按钮,就自动将当前的报表页面以图片形式下载下来呢?

html2canvas库就能帮我们做到,无需后台支持,纯浏览器实现截图,即使页面有滚动条也是没问题的,截出来的图非常清晰。

这个库的维护时间非常长,早在2013年9月8日它就发布了第一个版本,比Vue的第一个版本(2013年12月8日)还要早。

截止到今天2020年12月18日,html2canvas库在github已经有22.3k star,在npm的周下载量也有506k,非常了不起!

上一次提交是在2020年8月9日,可见作者依然在很热情地维护着这个库,而且用TypeScript重构过,不过这个库的作者非常保守,哪怕已经持续不断地维护了7年,他在README里依然提到这个库目前还在实验阶段,不建议在生产环境使用。

事实上我很早就将这个库用在了生产环境,这篇文章就来分析下这个神奇和了不起的JavaScript库,看看它是怎么实现浏览器端截图的。

1 如何使用

在介绍html2canvas的原理之前,先来看看怎么使用它,使用起来真的非常简单,几乎是1分钟上手。

使用html2canvas只要以下3步:

  1. 安装
  2. 引入
  3. 调用

Step 1: 安装

npm i html2canvas

 

Step 2: 引入

随便在一个现代框架的工程项目中引入html2canvas

import html2canvas from 'html2canvas';

 

Step 3: 截图并下载

html2canvas就是一个函数,在页面渲染完成之后直接调用即可。

视图渲染完成的事件:

  1. Angular的ngAfterViewInit方法
  2. React的componentDidMount方法
  3. Vue的mounted方法

 

可以只传一个参数,就是你要截图的DOM元素,该函数返回一个Promise对象,在它的then方法中可以获取到绘制好的canvas对象,通过调用canvas对象的toDataURL方法就可以将其转换成图片。

拿到图片的URL之后,我们可以

  1. 将其放到<img>标签的src属性中,让其显示在网页中;
  2. 也可以将其放到<a>标签的href属性中,将该图片下载到本地磁盘中。

我们选择后者。

1 html2canvas(document.querySelector('.main')).then(canvas => {
2   const link = document.createElement('a'); // 创建一个超链接对象实例
3   const event = new MouseEvent('click'); // 创建一个鼠标事件的实例
4   link.download = 'Button.png'; // 设置要下载的图片的名称
5   link.href = canvas.toDataURL(); // 将图片的URL设置到超链接的href中
6   link.dispatchEvent(event); // 触发超链接的点击事件
7 });

 

是不是非常简单?

参数

我们再来大致看一眼它的API,该函数的签名如下:

html2canvas(element: HTMLElement, options: object): Promise<HTMLCanvasElement>

 

options对象可选的值如下:

NameDefaultDescription
allowTaint false 是否允许跨域图像污染画布
backgroundColor #ffffff 画布背景颜色,如果在DOM中没有指定,设置“null”(透明)
canvas null 使用现有的“画布”元素,用来作为绘图的基础
foreignObjectRendering false 是否使用ForeignObject渲染(如果浏览器支持的话)
imageTimeout 15000 加载图像的超时时间(毫秒),设置为“0”以禁用超时
ignoreElements (element) => false 从呈现中移除匹配元素
logging true 为调试目的启用日志记录
onclone null 回调函数,当文档被克隆以呈现时调用,可以用来修改将要呈现的内容,而不影响原始源文档。
proxy null 用来加载跨域图片的代理URL,如果设置为空(默认),跨域图片将不会被加载
removeContainer true 是否清除html2canvas临时创建的克隆DOM元素
scale window.devicePixelRatio 用于渲染的缩放比例,默认为浏览器设备像素比
useCORS false 是否尝试使用CORS从服务器加载图像
width Element width canvas的宽度
height Element height canvas的高度
x Element x-offset canvas的x轴位置
y Element y-offset canvas的y轴位置
scrollX Element scrollX 渲染元素时使用的x轴位置(例如,如果元素使用position: fixed)
scrollY Element scrollY 渲染元素时使用的y轴位置(例如,如果元素使用position: fixed)
windowWidth Window.innerWidth 渲染元素时使用的窗口宽度,这可能会影响诸如媒体查询之类的事情
windowHeight Window.innerHeight 渲染元素时使用的窗口高度,这可能会影响诸如媒体查询之类的事情

忽略元素

options有一个ignoreElements参数可以用来忽略某些元素,从渲染过程中移除,除了设置该参数外,还有一种忽略元素的方法,就是在需要忽略的元素上增加data-html2canvas-ignore属性。

<div data-html2canvas-ignore>Ignore element</div>

 

2 基本原理

介绍完html2canvas的使用,我们先来了解下它的基本原理,然后再分析细节实现。

它的基本原理其实很简单,就是去读取已经渲染好的DOM元素的结构和样式信息,然后基于这些信息去构建截图,呈现在canvas画布中。

它无法绕过浏览器的内容策略限制,如果要呈现跨域图片,需要设置一个代理。

3 主流程 html2canvas方法

基本原理很简单,但源码里面其实东西很多,我们一步一步来,先找到入口,然后慢慢调试,走一遍大致的流程。

寻找入口文件

拉取到源码,有很多方法可以找到入口文件:

  • 方法一:最简单的方法是直接全局搜索html2canvas,这种方法效率很低,而且要碰运气,不推荐
  • 方法二:在项目中引入这个库,调用它,跑起来,并在该方法前面打断点进行调试,一般能精确地找到入口文件,推荐
  • 方法三:观察下是否有webpack.config.js或者rollup.config.js的构建工具的配置文件,然后在配置文件中找到精确的入口文件(一般是entryinput之类的属性),推荐
  • 方法四:直接扫一眼目录结构,一般入口文件在src/core/packages之类的目录下,文件名是index或者main,或者是模块的名字,有经验的话可以用这个方法,找起来很快,强烈推荐

方法一:全局搜索

最简单最容易想到的的方法,就是全局搜索关键字html2canvas,因为我们在不了解html2canvas的实现之前,我们接触到的关键字就只有这一个。

但是全局搜索运气不好的话,很可能搜出来很多结果,在里面找入口文件费时费力,比如:

42个文件285个结果,找起来很麻烦,不推荐。

方法二:打断点

在调用html2canvas的地方打一个断点。

然后在执行到断点处时,点击向下的小箭头,进入该方法。

因为在开发环境,很快我们就能发现入口文件和入口方法在哪儿,这里显示的是html2canvas文件,实际上这个文件是构建之后的文件,但是这个文件的上下文给我们提供了找入口方法的信息,这里我们发现了renderElement方法。

这时我们可以尝试全局搜索这个方法,很幸运直接找到了

推荐阅读