浅析js实现网页截图的两种方式


Posted in Javascript onNovember 01, 2019

Web端的截图(生成图片)并不算是个高频的需求,资料自然也不算多,查来查去,也不过Canvas 和 SVG两种实现方案,原理大概相似,都非真正义上的截图而是把DOM转为图片,然而实现方式却截然不同。

Canvas 实现

如何将dom转换成canvas图片?自然是要一点点画到canvas里,想想都是件麻烦事。通过分析github的知名截图库 niklasvh/html2canvas (7k+ star)的源码,梳理了其大致的思路:

  • 递归取出目标模版的所有DOM节点,填充到一个rederList,并附加是否为顶层元素/包含内容的容器 等信息
  • 通过z-index postion float等css属性和元素的层级信息将rederList排序,计算出一个canvas的renderQueue
  • 遍历renderQueue,将css样式转为setFillStyle可识别的参数,依据nodeType调用相对应canvas方法,如文本则调用fillText,图片drawImage,设置背景色的div调用fillRect等
  • 将画好的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 style="background-color: #ccc;width: 200px;height: 200px;" >Hello World</h1>
  </div>
//此代码仅在chrome测试下通过
function html2Svg (domStr) { 
      //创建模版字符串
      var svgXML=
      `<svg xmlns="http://www.w3.org/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'})
      //利用DOMURL.createObjectURL取出对象
      var url = window.URL.createObjectURL(svg);
      var img = new Image()
      img.src = url
      return img
    }

// 由于`foreignObject`只能引用XML文档,
// 所以我们需要对DOM进行格式化
function generateXML (domStr) { 
    var doc = document.implementation.createHTMLDocument('');
      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的小哥写了一个名为rasterizeHTML.js 的库,通过一系列的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已经被用于知乎-意见反馈功能。

参考

源码https://developer.mozilla.org/en-US/docs/Web/API/CanvasAPI/DrawingDOMobjectsintoacanvas

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery的html(data)方法与&amp;lt;script&amp;gt;脚本块的解决方法
Mar 09 Javascript
JS使用for循环遍历Table的所有单元格内容
Aug 21 Javascript
用模版生成HTML的的框架jquery.tmpl使用详解
Jan 07 Javascript
使用jQuery实现更改默认alert框体
Apr 13 Javascript
jQuery包裹节点用法完整示例
Sep 13 Javascript
在html中引入外部js文件,并调用带参函数的方法
Oct 31 Javascript
基于JS实现html中placeholder属性提示文字效果示例
Apr 19 Javascript
微信小程序6位或多位验证码密码输入框功能的实现代码
May 29 Javascript
小程序实现选择题选择效果
Nov 04 Javascript
vue-cli脚手架引入弹出层layer插件的几种方法
Jun 24 Javascript
countup.js实现数字动态叠加效果
Oct 17 Javascript
JS实现点星星消除小游戏
Mar 24 Javascript
javascript使用链接跨域下载图片
Nov 01 #Javascript
async/await让异步操作同步执行的方法详解
Nov 01 #Javascript
浅谈Three.js截图并下载的大坑
Nov 01 #Javascript
vue中使用vee-validator完成表单校验方案
Nov 01 #Javascript
解决vue的过渡动画无法正常实现问题
Oct 31 #Javascript
VUE单页面切换动画代码(全网最好的切换效果)
Oct 31 #Javascript
js new Date()实例测试
Oct 31 #Javascript
You might like
织梦模板标记简介
2007/03/11 PHP
如何用PHP实现插入排序?
2013/04/10 PHP
php实现的返回数据格式化类实例
2014/09/22 PHP
Ajax执行顺序流程及回调问题分析
2012/12/10 Javascript
JS实现部分HTML固定页面顶部随屏滚动效果
2015/12/24 Javascript
表单元素值获取方式js及java方式的简单实例
2016/10/15 Javascript
让编辑器支持word复制黏贴、截屏的js代码
2016/10/17 Javascript
iOS + node.js使用Socket.IO框架进行实时通信示例
2017/04/14 Javascript
jQuery实现按比例缩放图片的方法
2017/04/29 jQuery
javascript+html5+css3自定义弹出窗口效果
2017/10/26 Javascript
如何选择适合你的JavaScript框架
2017/11/20 Javascript
解决Linux无法正常安装与卸载Node.js的方法
2018/01/19 Javascript
Vue使用lodop实现打印小结
2019/07/06 Javascript
微信小程序移动拖拽视图-movable-view实例详解
2019/08/17 Javascript
微信小程序实现比较功能的方法汇总(五种方法)
2020/03/07 Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
2020/08/27 Javascript
[02:44]2014DOTA2 国际邀请赛中国区预选赛 大神红毯秀
2014/05/25 DOTA
[56:41]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 Newbee vs OG
2018/04/01 DOTA
python实现获取客户机上指定文件并传输到服务器的方法
2015/03/16 Python
python编程开发之textwrap文本样式处理技巧
2015/11/13 Python
Python实现带百分比的进度条
2016/06/28 Python
pip install urllib2不能安装的解决方法
2018/06/12 Python
Python运行不显示DOS窗口的解决方法
2018/10/22 Python
Python Flask框架扩展操作示例
2019/05/03 Python
卸载tensorflow-cpu重装tensorflow-gpu操作
2020/06/23 Python
Python turtle库的画笔控制说明
2020/06/28 Python
加拿大著名时装品牌:SOIA & KYO
2016/08/23 全球购物
英国建筑用品在线:Building Supplies Online(BSO)
2018/04/30 全球购物
下述程序的作用是计算机数组中的最大元素值及其下标
2012/11/26 面试题
儿科护士实习自我鉴定
2013/10/17 职场文书
幼儿园家长会欢迎词
2014/01/09 职场文书
人身损害赔偿协议书范本
2014/09/27 职场文书
领导干部群众路线教育实践活动剖析材料
2014/10/10 职场文书
员工辞退通知书
2015/04/17 职场文书
win10下go mod配置方式
2021/04/25 Golang
如何用JavaScript实现一个数组惰性求值库
2021/05/05 Javascript