浅析js实现⽹页截图的两种⽅式
Web端的截图(⽣成图⽚)并不算是个⾼频的需求,资料⾃然也不算多,查来查去,也不过Canvas 和 SVG两种实现⽅案,原理⼤概相似,都⾮真正义上的截图⽽是把DOM转为图⽚,然⽽实现⽅式却截然不同。
Canvas 实现
如何将dom转换成canvas图⽚?⾃然是要⼀点点画到canvas⾥,想想都是件⿇烦事。通过分析github的知名截图库 (7k+ star)的源码,梳理了其⼤致的思路:
递归取出⽬标模版的所有DOM节点,填充到⼀个rederList,并附加是否为顶层元素/包含内容的容器等信息
通过z-index postion float等css属性和元素的层级信息将rederList排序,计算出⼀个canvas的renderQueue
遍历renderQueue,将css样式转为setFillStyle可识别的参数,依据nodeType调⽤相对应canvas⽅法,如⽂本则调⽤fillText,图⽚drawImage,设置背景⾊的div调⽤fillRect等
将画好的canvas填充进页⾯
svg和canvas的区别⽆论是排序优先级的计算还是从css到canvas的转换,毫⽆疑问都是些巨⿇烦的事,尤其是放在真实的业务场景⾥,DOM模版中往往会包含复杂的样式与排版,html2canvas ⾜⾜⽤了20多个js来实现这层转换,复杂成度可见⼀斑。索性,我们不需要再重新造⼀遍轮⼦。
使⽤canvas转化的话灵活性较⾼,环境依赖上也只需要确保浏览器⽀持canvas就可以了,但它有个显著的缺点:慢。原因⾃然是因为⼤量的计算与递归调⽤,这是⽆可避免的。不过html2canvas代码中⼤量使⽤了Promise,所以html2canvas ⽀持异步操作。
限制:
⽆法跨域跨域资源
⽆法渲染iframe,flash等内容,但⽬前⽀持svg
值得⼀提的是,尽管html2canvas主页表⽰它还处于实验室环境,但⾃14年起便已经被Twitter 等⽤在了⽣产环境,所以虽然有诸多限制,稳定性应该还是保障的。
canvas如此复杂,那么有没有⼀种更简单的⽅法呢?
⾃然是有的,那便是SVG
SVG实现
⾸先,svg本来就是⽮量图形;其次,svg是可以⽤xml描述的;再其次,⽤来描述svg的标签⾥有个 foreignObject标签,这个标签可以加载其它命名空间的xml(xhtml)⽂档。也就是说,如果使⽤svg的话,我们不再需要⼀点点的遍历,转换节点;不⽤再计算复杂的元素优先级,只需要⼀股脑的将要渲染的DOM扔进<foreignObject></foreignObject>就好了,剩下的就交给浏览器去渲染。
让我们理⼀理思路:
⾸先,我们要声明⼀个基础的svg模版,这个模版需要⼀些基础的描述信息,最重要的,它要有<foreignObject>
</foreignObject>这对标签
将要渲染的DOM模版模版嵌⼊foreignObject
利⽤Blob构建svg图像
取出URL,赋值给
<div id='text'>
<h1 >Hello World</h1>
</div>
//此代码仅在chrome测试下通过
function html2Svg (domStr) {
//创建模版字符串
var svgXML=
`<svg xmlns="/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">${generateXML(html)}</foreignObject>
</svg>`
//利⽤Blob创建svg
var svg = new Blob([svgXML], {type: 'image/svg+xml'})
/
/利⽤ateObjectURL取出对象
var url = ateObjectURL(svg);
var img = new Image()
img.src = url
return img
}
// 由于`foreignObject`只能引⽤XML⽂档,
// 所以我们需要对DOM进⾏格式化
function generateXML (domStr) {
var doc = ateHTMLDocument('');
doc.write(html);
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
doc = parseStyle(doc)
console.log(doc)
html = (new XMLSerializer).serializeToString(doc).replace('<!DOCTYPE html>','');
return html
}
可以看到按这个思路来实现⾮常简单,并且没有了复杂的计算和递归,渲染速度⾃然要优于前者。然⽽使⽤svg,需要考虑诸多的限制问题。⼀个最为严肃的问题在于:SVG⽆法加载外部资源,也就是说,在svg⾥⾯,⽆论是还是或者css中的背景图,这些资源都是⽆法加载的。在使⽤canvas实现时,因为我们是⼀个node⼀个node去画,所以不存在资源引⽤的问题。但使⽤svg实现,相当于我们把⽂档交给SVG再来来渲染⼀遍,这对于我们来说是其实是⽆法控制的⿊盒操作,是受SVG限制的
万幸,⼀个昵称为Christoph Burgmer的⼩哥写了⼀个名为的库,通过⼀系列的hack技巧替我们绕过了许多限制。我知道你很好奇他是怎么做到的。简单来讲,rasterizeHTML.js在我们的基础实现上做了这些hack:
将<img/>的url 转为 dataURI
将background-color从style中取出,修改url后重新插⼊样式表
将link的的样式通过ajax down下来然后注⼊<style></sytle>
详见源码...
当然, rasterizeHTML.js能帮我们做的也不过是处理资源引⽤问题和浏览器兼容问题,更多的SVG的限制是⽆法绕过的,该库的⽂档正式列出了⾜⾜⼀整页的限制,让⼈读完后⼼中⼀凉。⽐如:
跨域资源⽆法加载
如lazyload等通过js加载的资源⽆法加载
内联或js操作background-image⽆法加载
详见⽂档
思考下rasterizeHTML.js的原理便可理解这些限制⽆法避免的原因: rasterizeHTML.js只能对已经存在的静态资源进⾏处理,⽽对js动态⽣成并不能实时处理。
⽬前rasterizeHTML.js已经被⽤于知乎-意见反馈功能。
参考
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。