zoukankan      html  css  js  c++  java
  • 初探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为边界,会存在大量的空白),可是,我的卡组只是占了页面的一小部分,我只想要卡组的部分啊。这只需要把document.body修改成你想要截取的元素就行了。完整的demo如下:

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style>
        .card {
           354px;
        }
    
        .card img.cover {
           100%;
        }
    
        .card .user-name {
          text-align: center;
        }
      </style>
    </head>
    <body>
      <div class="app">
        <div class="card">
          <img class="cover" src="https://ok.166.net/audiozhurong/nielian/2018/07/04/0F231F5194F0F2F1530673285.png" alt="">
          <div class="user-name">素问小姐姐</div>
        </div>
      </div>
      <script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js">
      </script>
      <script>
        html2canvas(document.querySelector('.card'), {
          allowTaint: true // 允许加载跨域资源
        }).then(function(canvas) {
          document.body.appendChild(canvas);
        });
      </script>
    </body>
    </html>
    

    效果图:

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

  • 相关阅读:
    uva 10369 Arctic Network
    uvalive 5834 Genghis Khan The Conqueror
    uvalive 4848 Tour Belt
    uvalive 4960 Sensor Network
    codeforces 798c Mike And Gcd Problem
    codeforces 796c Bank Hacking
    codeforces 768c Jon Snow And His Favourite Number
    hdu 1114 Piggy-Bank
    poj 1276 Cash Machine
    bzoj 2423 最长公共子序列
  • 原文地址:https://www.cnblogs.com/yugege/p/7246519.html
Copyright © 2011-2022 走看看