JavaScript截屏功能的实现代码


Posted in Javascript onJuly 28, 2017

最近参与了网易炉石盒子的相关页面开发,在做卡组分享页(地址:炉石盒子卡组分享),有个需求:用户可以把这个卡组以图片的形式分享给好友。最初的的做法是使用服务器把该页面转换成图片,然后把图片地址返回给前端。嗯,这样也挺好的啊,而且服务器还可以对转换出来的图片进行缓存,下次请求可以直接返回图片地址了。原理上是毫无毛病的。然而,问题来了,后台转换的图片和页面内容偶尔不一致,有时候会少了一一些内容,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的原理。

总结

以上所述是小编给大家介绍的JavaScript截屏功能的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JAVASCRIPT对象及属性
Feb 13 Javascript
JavaScript DOM学习第六章 表单实例
Feb 19 Javascript
js判断屏幕分辨率的代码
Jul 16 Javascript
js this函数调用无需再次抓获id,name或标签名
Mar 03 Javascript
详解JavaScript中this关键字的用法
May 26 Javascript
如何解决手机浏览器页面点击不跳转浏览器双击放大网页
Jul 01 Javascript
JS中使用DOM来控制HTML元素
Jul 31 Javascript
jQuery事件用法详解
Oct 06 Javascript
KnockoutJS 3.X API 第四章之表单textInput、hasFocus、checked绑定
Oct 11 Javascript
JS判断鼠标进入容器的方向与window.open新窗口被拦截的问题
Dec 23 Javascript
JavaScript事件对象深入详解
Dec 30 Javascript
微信小程序获取用户绑定手机号方法示例
Jul 21 Javascript
BootStrap selectpicker后台动态绑定数据的方法
Jul 28 #Javascript
详解angularjs的数组传参方式的简单实现
Jul 28 #Javascript
js 获取html5的data属性实现方法
Jul 28 #Javascript
jQuery获取table表中的td标签(实例讲解)
Jul 28 #jQuery
浅谈JS中的常用选择器及属性、方法的调用
Jul 28 #Javascript
js原生代码实现轮播图的实例讲解
Jul 28 #Javascript
js弹性势能动画之抛物线运动实例详解
Jul 27 #Javascript
You might like
PHP_Flame(Version:Progress)的原代码
2006/10/09 PHP
MySQL时间字段究竟使用INT还是DateTime的说明
2012/02/27 PHP
几个有用的php字符串过滤,转换函数代码
2012/05/01 PHP
PHP preg_match实现正则表达式匹配功能【输出是否匹配及匹配值】
2017/07/19 PHP
PHP实现的多维数组去重操作示例
2018/07/21 PHP
Laravel学习笔记之Artisan命令生成自定义模板的方法
2018/11/22 PHP
laravel框架创建授权策略实例分析
2019/11/22 PHP
用js判断浏览器是否是IE的比较好的办法
2007/05/08 Javascript
js判断客户端是iOS还是Android等移动终端的方法
2013/12/11 Javascript
jquery删除提示框弹出是否删除对话框
2014/01/07 Javascript
JS生成不重复随机数组的函数代码
2014/06/10 Javascript
Javascript基础教程之比较操作符
2015/01/18 Javascript
Angular懒加载机制刷新后无法回退的快速解决方法
2016/08/30 Javascript
javascript 初学教程及五子棋小程序的简单实现
2017/07/04 Javascript
Vue.js仿微信聊天窗口展示组件功能
2017/08/11 Javascript
ionic3 懒加载
2017/08/16 Javascript
Vue 滚动行为的具体使用方法
2017/09/13 Javascript
微信小程序中时间戳和日期的相互转换问题
2018/07/09 Javascript
JS中实现隐藏部分姓名或者电话号码的代码
2018/07/17 Javascript
对angular2中的ngfor和ngif指令嵌套实例讲解
2018/09/12 Javascript
angular4强制刷新视图的方法
2018/10/09 Javascript
Vue 用Vant实现时间选择器的示例代码
2019/10/25 Javascript
Vue 技巧之控制父类的 slot
2020/02/24 Javascript
Python中的字符串替换操作示例
2016/06/27 Python
Python爬取梨视频的示例
2021/01/29 Python
高中毕业自我鉴定
2013/12/19 职场文书
外企财务年会演讲稿
2014/01/03 职场文书
蛋糕店的商业计划书范文
2014/01/27 职场文书
揭牌仪式策划方案
2014/05/28 职场文书
2014世界杯球队球队口号
2014/06/05 职场文书
2014年应急管理工作总结
2014/11/26 职场文书
2015年先进个人自荐书
2015/03/24 职场文书
接待员岗位职责范本
2015/04/15 职场文书
2016毕业实习单位评语大全
2015/12/01 职场文书
Python学习之时间包使用教程详解
2022/03/21 Python
Java 超详细讲解十大排序算法面试无忧
2022/04/08 Java/Android