Home  >  Article  >  Web Front-end  >  JavaScript implements screenshot function

JavaScript implements screenshot function

巴扎黑
巴扎黑Original
2017-08-14 14:07:381943browse

I think the idea of ​​using JS to take screenshots is very ridiculous. First of all, JS does not have the permission to call the screenshot function of the operating system. Secondly, the browser (BOM) does not provide a relevant screenshot interface. After some tossing, I got some ideas. The following is a brief introduction to the implementation code of the js screenshot function through example code. Let’s take a look.

Recently participated in the development of related pages of NetEase Hearthstone Box and was working on a card group sharing page (Address: Hearthstone Box Card group sharing), there is a requirement: users can share this card group with friends in the form of pictures. The original approach was to use the server to convert the page into an image, and then return the image address to the front end. Well, this is pretty good, and the server can also cache the converted images, and the next request can directly return the image address. There is nothing wrong with it in principle. However, a problem arises. The pictures converted in the background are occasionally inconsistent with the page content, and sometimes some content is missing. The PM sister is very unhappy and says that this problem must be solved. Anyway, the interface for converting pages into images is done in the background, it’s none of my business! Just when I was secretly happy, something tragic happened. A colleague in the background said that because some content in the page is loaded asynchronously (for example, the QR code at the bottom is generated through canvas), the server conversion is unstable. Sometimes Asynchronously rendered content cannot be intercepted. To put it bluntly, he has no way to solve this problem. Let’s change the front end. Who told the front end to use asynchronous rendering? Finally, the leader asked me to try if I could directly use JS to take screenshots, which would not only reduce the pressure on the server, but also solve the above bug.

At first, I thought the idea of ​​using JS to take screenshots was very ridiculous (blame my ignorance, the front-end has developed too fast in recent years): firstly, JS does not have permission to call the screenshot function of the operating system, and secondly, The browser (BOM) also does not provide a relevant screenshot interface. What should I do? What should I do? If you have any questions, please contact Google. Then I searched for: JS html to png, and then I found this: render-html-to-an-image. I'm starting to get some ideas. Someone in the answer mentioned that you can convert DOM into canvas, huh! It’s Canvas again! I couldn't help but get excited. It was really difficult to find a way out of the mountains and rivers, but there was still a bright future in another village! Then I searched for dom to canvas and came to the well-known mdn document Drawing_DOM_objects_into_a_canvas. Then I started to read the document seriously. As mentioned at the beginning of the document, you cannot convert DOM into canvas, but you can convert DOM into SVG, and then draw the SVG into the canvas. Some people may ask, why do we need to convert dom to svg first? This may be because SVG uses XML representation and its structure is consistent with DOM.
The following is the step by step tutorial from the official document:

1. The media type of the Blob must be "image/svg+xml"

2.Required An svg element

3. Insert a foreignObject element

inside the svg element. 4. Put the html

that conforms to the specification into the foreignObject element. Converting dom to canvas is as simple as the above steps. The following is a simple demo given in the document:


<!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(&#39;canvas&#39;);
 var ctx = canvas.getContext(&#39;2d&#39;);
 var data = &#39;<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">&#39; +
  &#39;<foreignObject width="100%" height="100%">&#39; +
  &#39;<p xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">&#39; +
  &#39;<em>I</em> like &#39; +
  &#39;<span style="color:white; text-shadow:0 0 2px blue;">&#39; +
  &#39;cheese</span>&#39; +
  &#39;</p>&#39; +
  &#39;</foreignObject>&#39; +
  &#39;</svg>&#39;;
 var DOMURL = window.URL || window.webkitURL || window;
 var img = new Image();
 var svg = new Blob([data], {type: &#39;image/svg+xml&#39;});
 var url = DOMURL.createObjectURL(svg);
 img.onload = function() {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);
 }
 img.src = url;
</script>
</body>
</html>

Copy the code and run it. Wow, it’s so cool. Two cool lines appear on the browser. There’s word art!

Well, it turns out that converting dom to canvas is so easy? Then I use document.body.innerHTML to take out all the DOM in the body and put it in the foreignObject element. Isn’t it OK and the entire page is intercepted?

The demo is just a Hello World, but the Dom structure in the actual project is much more complicated than this. For example, external style sheets, pictures, and some tags may not comply with the xml specification (such as Missing closing tag, etc.). Here is a simple example. .container does not use inline styles, but is defined in the style tag. The font is red. After it is converted into an image, the style does not take effect.


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

Since external styles do not take effect, we can traverse all DOM elements through JS and add all styles to inline styles through the element.style object. This idea sounds good, but I really can't write the function that converts external styles into inline styles. The demand is tight, and I don't have much time to mess around, so I want to look for any ready-made libraries. So I went to Google again. Fortunately, I quickly found a library called html2canvas. It is a great library, very powerful, but very simple to use. With such a simple method, I can take a screenshot of my entire page:


function convertHtml2Canvas() {
  html2canvas(document.body, {
   allowTaint: false,
   taintTest: true
  }).then(function(canvas) {
   document.body.appendChild(canvas);
  }).catch(function(e) {
   console.error(&#39;error&#39;, 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: &#39;block&#39;,
     position: &#39;absolute&#39;,
     top: 0,
     left: 400 + &#39;px&#39;
    });
   }
  }).catch(function(e) {
   console.error(&#39;error&#39;, e);
  });
 }

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

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

The above is the detailed content of JavaScript implements screenshot function. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn