浅析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 相关文章推荐
封装好的省市地区联动控件附下载
Aug 13 Javascript
jQuery jqgrid 对含特殊字符json 数据的 Java 处理方法
Jan 01 Javascript
JavaScript高级程序设计 阅读笔记(十八) js跨平台的事件
Aug 14 Javascript
js实现有时间限制消失的图片方法
Feb 27 Javascript
深入理解JavaScript系列(31):设计模式之代理模式详解
Mar 03 Javascript
JavaScript三元运算符的多种使用技巧
Apr 16 Javascript
javascript使用shift+click实现选择和反选checkbox的方法
May 04 Javascript
js实现发送验证码后的倒计时功能
May 28 Javascript
Angular中ng-bind和ng-model的区别实例详解
Apr 10 Javascript
JS加密插件CryptoJS实现AES加密操作示例
Aug 16 Javascript
vue 实现左右拖拽元素并且不超过他的父元素的宽度
Nov 30 Javascript
Vue+Java+Base64实现条码解析的示例
Sep 23 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
简单采集了yahoo的一些数据
2007/02/14 PHP
PHP session常见问题集锦及解决办法总结
2007/03/18 PHP
基于PHP对XML的操作详解
2013/06/07 PHP
php解析url的三个示例
2014/01/20 PHP
PHP安装GeoIP扩展根据IP获取地理位置及计算距离的方法
2016/07/01 PHP
怎么用javascript进行拖拽
2006/07/20 Javascript
jquery中dom操作和事件的实例学习 下拉框应用
2011/12/01 Javascript
在Ubuntu上安装最新版本的Node.js
2014/07/14 Javascript
JS获取当前网页大小以及屏幕分辨率等
2014/09/05 Javascript
js实现图片漂浮效果的方法
2015/03/02 Javascript
javascript实现信息增删改查的方法
2015/07/25 Javascript
JS实现的仿QQ空间图片弹出效果代码
2016/02/23 Javascript
JS代码实现百度地图 画圆 删除标注
2016/10/12 Javascript
Bootstrap Tree View简单而优雅的树结构组件实例解析
2017/06/15 Javascript
Bootstrap 模态框(Modal)带参数传值实例
2017/08/20 Javascript
Angular2管道Pipe及自定义管道格式数据用法实例分析
2017/11/29 Javascript
Vue修改mint-ui默认样式的方法
2018/02/03 Javascript
React组件重构之嵌套+继承及高阶组件详解
2018/07/19 Javascript
浅谈Angular单元测试总结
2019/03/22 Javascript
JavaScript计算正方形面积
2019/11/26 Javascript
Vue 3自定义指令开发的相关总结
2021/01/29 Vue.js
跟老齐学Python之玩转字符串(2)更新篇
2014/09/28 Python
对Python中的@classmethod用法详解
2018/04/21 Python
django 删除数据库表后重新同步的方法
2018/05/27 Python
python统计指定目录内文件的代码行数
2019/09/19 Python
搭建pypi私有仓库实现过程详解
2020/11/25 Python
html特殊符号示例 html特殊字符编码对照表
2014/01/14 HTML / CSS
HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码
2020/04/10 HTML / CSS
Born鞋子官网:Born Shoes
2017/04/06 全球购物
新闻专业应届生求职信
2013/10/31 职场文书
毕业设计致谢语
2015/05/14 职场文书
生日赠语
2015/06/23 职场文书
如何写通讯稿
2015/07/22 职场文书
如何使用flask将模型部署为服务
2021/05/13 Python
简单谈谈Python面向对象的相关知识
2021/06/28 Python
opencv用VS2013调试时用Image Watch插件查看图片
2021/07/26 Python