最近做了个实现网页缩略图的项目,其中不免需要用到网页截屏。
一开始想的是看看能不能在前端直接实现截图,因为Web端的截图并生成图片并不算是一个高频的需求,网上资料自然也不算多,查来查去,发现JavaScript 目前还不能实现真正意义上的网页截屏,未来如果能够实现,也一定是浏览器提供了相关接口,JS 去调用这些接口。
既然js不能实现真正意义上的截屏,那我们能做的只有通过拿到DOM的一些信息利用canvas来生成图片。
如何将dom转换成canvas图片?自然是要一点点画到canvas里,想想都是件麻烦事。网上找来找去看看有没有现成的轮子,找到了html2canvas,通过分析 niklasvh/html2canvas,梳理了其大致的思路:
遍历页面中的所有元素,提取DOM节点,填充到一个rederList,并附加是否为顶层元素/包含内容的容器等信息
通过提取的css属性和元素的层级信息将rederList排序,计算出一个canvas的renderQueue
遍历renderQueue,将css样式转为setFillStyle可识别的参数,依据nodeType调用相对应canvas方法,将节点对应到 canvas 上。
这个方案听起来就很复杂了,无论是排序优先级的计算还是从css到canvas的转换,毫无疑问都是些巨麻烦的事,尤其是放在真实的业务场景里,DOM模版中往往会包含复杂的样式与排版,html2canvas 足足用了20多个js来实现这层转换,复杂成度可见一斑。不过这个库封装的不错,使用起来比较简单:
html2canvas(document.body).then(function(canvas) {
canvas.id = 'screenshotCanvas'
document.body.appendChild(canvas)
});
此时,页面的截图已经 append 到了 body 中了。虽然过程是很复杂,但是已经有了这些轮子,我们了解原理就好,人家有现成的库可以用,那咱们就不要再花费力气去造轮子,不好玩。
说了这么多这个canvas的方案,但是我最终还是没有选择它,因为我的网页里面有不少图片,都是外域资源,而这套方案我认为最大的问题就是:无法跨域加载资源 (虽然html2canvars补充了方案来弥补这个问题,但是对于我的项目实现起来成本都太高)
忘了说,在研究html2canvars的过程中我还发现了一个库叫rasterizeHTML.js。知乎的意见反馈功能里面的截图就是使用的这个库,原理有点类似,不过使用的是svg,所以我还是没有选择它,它也没法绕过跨域请求资源这个问题。
最后只能从node端使用 phantomJS 来实现了。phantomJS 它提供了一个截屏函数,通过它可以整屏获取页面截图,而且他支持的格式也比较多:JPG/PNG/GIF/PDF。通过简单的两句命令就可以把一个网页截取下来:
// render.js
var webPage = require('webpage')
var page = webPage.create()
page.viewportSize = { width: 1920, height: 1080 }
page.open("http://www.iqiyi.com", function start(status) {
page.render('iqiyi_home.jpeg', {format: 'jpeg', quality: '100'})
phantom.exit()
})
安装 phantomjs 之后执行下上面的文件:
phantomjs render.js
调用完你会发现,图片已经保存到了同目录下。
选择好方案后还是踩了不少坑,第一个就是我得想办法让它和node通信,不然我没法通过前端只传一个需要被截图的链接给node就能实现截图。
于是接着在网上找,发现又是这么复杂,动不动就是websocket的方式等,只能接着找轮子,还好有诸多前辈已经实现好了,万花丛中选择了一个比较适合我项目的叫做phantom的库解决了这问题。
走到这一步以为万事具备了,然后开干,发现截了张白屏给我,一开始以为是要截的网页数据没有加载完,于是delay了一会再去截图,发现还是白屏,这就很绝望了。
走到这一步再放弃就不好玩了,最终经过长久的debug发现,原来phantomJS没有支持到promise,而我网页请求数据走的是fetch api,phantomJS模拟浏览器打开我的网页,数据一直请求不到,打开的网页是个空的,截图自然就变成白屏了。
最后的结局是好的,就是再对promise 做了一下polyfill,实现了我想要的截图。过程也是好的,作为前端菜鸟,能学到的简直不能再多了。(最近看了看刚出来的Headless chrome,或许以后的截图我就用不到phantom了)