初探JavaScript的截屏实现

简介:

最近参与了网易炉石盒子的相关页面开发,在做卡组分享页(地址:炉石盒子卡组分享),有个需求:用户可以把这个卡组以图片的形式分享给好友。最初的的做法是使用服务器把该页面转换成图片,然后把图片地址返回给前端。嗯,这样也挺好的啊,而且服务器还可以对转换出来的图片进行缓存,下次请求可以直接返回图片地址了。原理上是毫无毛病的。然而,问题来了,后台转换的图片和页面内容偶尔不一致,有时候会少了一一些内容,PM姐姐就很不爽了,说这个问题一定要解决。反正页面转成图片的接口是后台做的,关我luan事啊!就在暗暗自喜的时候,悲催的事情发生的,后台的同事说,因为页面里面有些内容是异步加载出来的(比如底部的二维码是通过canvas生成的),服务器转换不稳定,有时候对异步渲染的内容无法截取。说白了,就是这问题他没有办法解决,前端去改吧,谁叫前端用了异步渲染呢?最后Leader让我尝试能不能直接用JS进行截图了,这样既可以减轻服务器的压力,又可以解决上面bug。
一开始,我觉得使用JS截图的想法是非常荒谬的(怪我无知咯,前端这几年发展的实在太快了):首先JS没有权限调用操作系统的截图功能,其次,浏览器(BOM)也没有提供相关的截图接口。我该怎么办呢、怎么办呢?有事找Google啊。然后搜索了一下: JS html to png ,然后来到就找到了这里:render-html-to-an-image。开始有思路了,回答中有人提到可以把dom转成canvas,嗯!又是Canvas!我不由得兴奋起来,真的是山重水复疑无路,柳暗花明又一村啊!然后再搜索一下 dom to canvas,来到了大家熟知的mdn的文档Drawing_DOM_objects_into_a_canvas。然后就开始认(zhuang)真(bi)的看文档。文档开头就说到,不可以把dom转成canvas,但是可以把dom转成svg,然后再把svg画到canvas里面去。也许有人会问,为什么要先把dom转成svg呢?这可能是因为svg使用xml表示、结构和dom一致吧。
下面就是官方文档的step by step的教程:

1.Blob的媒体类型必须是"image/svg+xml"
2.需要一个 svg 元素
3.在 svg 元素里面插入一个 foreignObject 元素
4.在 foreignObject 元素里面放入符合规范的 html

把dom转成canvas就这么简单,就上面几个步骤。下面是文档给出的一上简单的demo:

<!doctype html><html lang="en"><head>
  <meta charset="UTF-8">
  <title>Document</title></head><body><canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas><script>
  var canvas = document.getElementById('canvas');  var ctx = canvas.getContext('2d');  var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +    '<foreignObject width="100%" height="100%">' +    '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +    '<em>I</em> like ' +    '<span style="color:white; text-shadow:0 0 2px blue;">' +    'cheese</span>' +    '</div>' +    '</foreignObject>' +    '</svg>';  var DOMURL = window.URL || window.webkitURL || window;  var img = new Image();  var svg = new Blob([data], {type: 'image/svg+xml'});  var url = DOMURL.createObjectURL(svg);
  img.onload = function() {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
  }
  img.src = url;</script></body></html>

复制代码,运行一下,哇,帅呆了,浏览器上出现了超酷的两行艺术字呢!
嗯,原来dom转成canvas这么简单啊?那我通过 document.body.innerHTML 把body里面的所有dom取出来,然后放到 foreignObject 元素里面,不就OK了、把整个页面都截取下来了吗?
demo仅仅是个Hello World,但是实际项目中的Dom结构比这个复杂多了,比如,引入了外部样式表、图片、而且还可能某些标签不符合xml规范(如缺少闭合标签等)。下面的举个简单的例子,.container不是使用行内样式的,而是在style标签里面定义,字体红色,转成图片后,样式不生效。

<!doctype html><html lang="en"><head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .container {      color: red;
    }  </style></head><body><div class="container" >
  Hello World!</div><canvas id="canvas" style="border:2px solid black;" width=200" height="200"></canvas><script>
  var canvas = document.getElementById('canvas');  var ctx = canvas.getContext('2d');  var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +    '<foreignObject width="100%" height="100%">' +    '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +    document.querySelector('.container').innerHTML +    '</div>' +    '</foreignObject>' +    '</svg>';  var DOMURL = window.URL || window.webkitURL || window;  var img = new Image();  var svg = new Blob([data], {type: 'image/svg+xml'});  var url = DOMURL.createObjectURL(svg);
  img.onload = function() {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
  }
  img.src = url;</script></body></html>

既然外部样式不生效,那我们可以通过JS遍历所有的dom元素,把全部的样式通过element.style对象添加到行内样式啊。这个思路听起来不错,但是,实现这个把外部样式转成行内样式的函数我还真写不出来啊。需求比较紧,也没有那 多时间去瞎折腾了,所以,就想找找有没有现成的库。于是又去google一下。很幸运, 一下子就搜到了一个叫做html2canvas的库,非常棒的一个库,很强大、但用法非常简单.就这么简单的方法,就可以把我的整个页面截图下来了:

function convertHtml2Canvas() {
    html2canvas(document.body, {
      allowTaint: false,
      taintTest: true
    }).then(function(canvas) {      document.body.appendChild(canvas);
    }).catch(function(e) {      console.error('error', e);
    });
  }

目前还有一个问题,就是这种方法默认是把整个页面截取下来的(就是说,会以你的innerHeight和innerWidth为边界,会存在大量的空白),可是,我的卡组只是占了页面的一小部分,我只想要卡组的部分啊。其实已经有了canvas就好办了,我们可以对它进行处理啊。大概思路是:1.把上面得到的canvas对象转成Blob并放到一个img元素。然后再把img.src绘制到canvas里面。这时候调用canvas.drawImage方法就可以截取我们想要的内容了。下面的两个函数分别是把canvas转成image以及反过来把image转成canvas。

// Converts canvas to an image
  function convertCanvasToImage(canvas) {    var image = new Image();
    image.src = canvas.toDataURL("image/png", 0.1);    return image;
  }  
  // Converts image to canvas; returns new canvas element
  function convertImageToCanvas(image, startX, startY, width, height) {    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    canvas.getContext("2d").drawImage(image, startX, startY, width, height, 0, 0, width, height);    return canvas;
  }

然后,再把我们上面的写的 convertHtml2Canvas 改成下面的:

function convertHtml2Canvas() {
    html2canvas(document.body, {
      allowTaint: false,
      taintTest: true
    }).then(function(canvas) {      var img = convertCanvasToImage(canvas);      document.body.appendChild(img);
      img.onload = function() {
        img.onload = null;
        canvas = convertImageToCanvas(img, 0, 0, 384, 696);
        img.src = convertCanvasToImage(canvas).src;
        $(img).css({
          display: 'block',
          position: 'absolute',
          top: 0,
          left: 400 + 'px'
        });
      }
    }).catch(function(e) {      console.error('error', e);
    });
  }

这时候就可以把它的页面的某部分内容进行截取下来了。效果如卡组分享测试页面。页面左边部分是DOM结构的,右边部分是则是使用html2canvas转换出来的图片。长得一模一样,毫无毛病哈。

关于JS页面截图的就写到这里啦,因为也只刚刚接触,很多东西也理解得不到位,欢迎各大神指点。后面会深入学习一下html2canvas的源码,进一步理解dom to canvas的原理。















本文转自xsster51CTO博客,原文链接: http://blog.51cto.com/12945177/1951668,如需转载请自行联系原作者




相关文章
|
JavaScript 前端开发
javascript深拷贝和浅拷贝以及实现方法(推荐)
javascript深拷贝和浅拷贝以及实现方法(推荐)
524 0
javascript深拷贝和浅拷贝以及实现方法(推荐)
|
JavaScript 前端开发
利用JavaScript实现二级联动
利用JavaScript实现二级联动 要实现JavaScript二级联动效果,首先要确定需要哪些技术: 二维数组 for in循环 new Option(text,value,true,true) add(option,null) onchange() 表单事件 HTML代码: &lt;!-- &lt;input type=&quot;text&quot; id=&quot;text&quot;&gt; --&gt; 请选择省份: &lt;select name=&quot;&quot; id=&quot;provinces&quot;&gt; &lt;!-- &lt;option value=&quot;江苏省&quot;&gt;江苏省&lt;/option&gt;
|
JavaScript 前端开发
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
167 0
|
移动开发 JavaScript weex
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
219 0
|
JavaScript
JS中实现或退出全屏
JS中实现或退出全屏
153 0
|
前端开发 JavaScript
前端:JS实现双击table单元格变为可编辑状态
前端:JS实现双击table单元格变为可编辑状态
365 0
|
JavaScript 算法 前端开发
【前端算法】JS实现数字千分位格式化
JS实现数字千分位格式化的几种思路,以及它们之间的性能比较
270 1
|
算法 前端开发 JavaScript
【前端算法】用JS实现快速排序
理解数组方法里面运用到的算法,splice 和 slice的区别
112 0
|
JavaScript 前端开发 算法
【前端算法】javaScript实现二分查找
如何使用JS实现一个合格的二分查找
189 0