Start with a list of solutions and a conclusion, and then turn on the verbose mode:

  1. Html2canvas plugin –> Canvas Canvas –> PNG/PDF/JPG –> A tag Download property (Solution 1: pure front-end solution)
  2. Use node plug-ins like phantom. Js or casper.js directly, use headless WebKit to simulate opening the page, export it as an image/PDF, then upload it to OSS, and return the corresponding link to the front end for downloading. (Scheme 2: Back-end scheme)

Conclusion: When THE HTML content is within the scope of drawing ability of Canvas, scheme 1 (version 2: BLOB method) is used; when the HTML content exceeds the scope of drawing ability of Canvas, Scheme 2 is used.

In the long-winded mode, it mainly describes some problems encountered in the development process of using scheme I and the process of exploring the causes of these problems and finding solutions.

= = = = = = = = = = = = = = = repetitive pattern line, under the open verbose mode = = = = = = = = = = = = = = = = = = =

intuitivelyCompare the two plans:

Scheme 1: only need a front-end participation + the introduction of a third-party plug-in can be done, and the link is not long.

Solution 2: The need for front-end and back-end collaboration + the project needs to start another Node environment + OSS resources + multi-user concurrency problems are relatively large both in terms of implementation complexity and cost.

Intuitive comparison results: under ideal conditions, obviously scheme 1 is better.

The problems encountered in the development of scheme 1

The most intuitive performance is: when the HTML page content is too much, the image download failure.

Here is the code for plan 1 (version 1)

Var base64 = canvas.todataURL (); var base64 = canvas.todataurl (); var link = document.createElement('a'); link.textContent = 'download image'; link.href = base64; link.download = "mypainting.jpeg"; link.click()Copy the code

There are two reasons for the failure: 1. The size of a base64 image can be downloaded using a tag href+ Download. The ability to draw a canvas is limited.

Then I threw out the following questions:

  1. Base64 picture local measurement is greater than 1.6m why not download down? How to define the specific zero bound point of download?
  2. What are the alternatives for downloading large images locally (larger than 1.6m, and 2M according to some information on the Internet)?
  3. What are the factors that affect the upper volume of Canvas? (Canvas width? High? Area? Color complexity?) What is the upper limit?

To answer these questions, I wrote a small demo (see the code at the end of this article) : there are two elements on the page, a button and a canvas canvas. Click the button to export the canvas as an image. We can change the width and height or color complexity of canvas to observe the rendering state and picture download state of canvas.

You can post the results of my experiment [Canvas canvas is rendered in pure black and the download method is base64 (the mode value in demo is base64)] :

Canvas Rendering status Base64 Length Picture downloadable or not Picture size 1000 x 32700 Canvas can be rendered. Base64 length 511403 Picture downloadable 1000 x 32800 Canvas cannot be rendered 2400 x 32700 Canvas can be rendered base64 length 1226803 image downloadable image size 1.4m 2600*32700 Canvas can be rendered Base64 length 2084390 image downloadable image size 1.6m 2700*32700 Canvas can be rendered base64 length 2152518 images cannot be downloaded 8200*32700 Canvas can not be renderedCopy the code

Answer question 1:

As can be seen from the experimental results, when the length of Base64 is 2084390, the image can be downloaded; when the length of Base64 is 2152518, the image download fails. As a first guess, this download limit is base64 length 2 X 1024 X 1024 = 2097152. So why did the download fail? Take a look at canvas.todataURL (), the definition of Data URLs in the MDN document

Data URLsIs the prefix is
The data: schemeURL, which allows content creators to embed small files into documents.

See this in frequently asked questions about Data URLs

Length limit


Although Firefox supports unlimited length
dataURLs, but there is nothing in the standard that says browsers must support arbitrary lengths
dataURIs,. For example, Opera 11 limits URLs to 65535 characters, which means data URLs are 65529 characters long (if you use plain text data:, rather than specifying a MIME type, 65529 characters long is the encoded length, Not the source file).

So data URLs were originally proposed for small files. Different browsers have their own character limits for the longest data URLs, and Chrome has a base64 length of 2M (not image size of 2M).

In addition, there is an issue on Chrome. In 2011, a friend responded to this problem, and the status of this issue is still available. Can go to check out: bugs.chromium.org/p/chromium/… . I can’t find the official chrome length limit for data URLs, so I can assume this is a long-standing Chrome bug.

Answer question 2:

From question 1, it can be seen that the reason why images cannot be downloaded is the length of data URL. There are two ways to solve this problem:

Method 1: Take the Base64 encoding and send it to the back end to be converted into an OSS network image like www.xxx.com/a.jpg. This method can be used, but it is not recommended to use this method because passing too long Base64 encoding requires the background to change some configuration such as the maximum size of a request, and the speed experience is very slow.

Method 2: convert canvas toBlob using canvas toBlob() and use url = url.createobjecturl (blob). Generate a URL that points to the BLOb image object for the BLOb object. The LENGTH of the URL is generally more than 40 (similar to blob:null/ 580f6DB8-8a79-43C1-baef-f07e84484492). As long as the actual measurement is canvas can render, it can be downloaded successfully in this way. (The actual test used canvas to draw a very complex picture, which is the colorful strange picture at the beginning of the article. It can be successfully exported through this method. The size of the picture is 196M.)

Scheme 1 (Version 2) code:

canvas.toBlob(function(blob) {
            url = URL.createObjectURL(blob);
            var link = document.createElement('a');
            link.textContent = 'download image';
            link.href = url;
            link.download = "mypainting.jpeg";
            link.click()
            // no longer need to read the blob so it's revoked
            URL.revokeObjectURL(url);
});
Copy the code

Answer question 3:

Canvas canvas volume will have an upper limit, and width, height, color complexity, computer graphics card performance,

Not related to:

  • Not very relevant to the area (width * height). Because in this example the 8200*32700 canvas can be rendered, but the 1000*32800 canvas cannot.
  • This volume can not be measured by the volume of the exported image. Through this method, the image with the size of 196M can be exported, which is already very large. However, if the canvas width and height are increased in all-black mode, the exported limit size is 4.8m.

Because the canvas volume is related to the color complexity of the picture and the performance of the computer graphics card, the width and height measured above are not representative. From my point of view, I can not find a specific value to quantify. (If you have better ideas, please feel free to comment).

In conclusion, among the two reasons for the failure of image download in solution 1, the problem of the upper limit of the base64 image download in the browser can be solved by BLOB (Solution 1 version 2), but the problem of the upper limit of the drawing ability of Canvas cannot be solved by the front end, and solution 2 is needed.

Solution 2: I believe this is the ultimate implementation of image downloading (although the implementation is a bit of a hassle). There are no restrictions, I have done similar projects before, using casperJS capture() function to do this, hundreds of MB of PDF export is not a problem. In the current project, because the background is Java, we will adopt the Java scheme to do this.

So back to the conclusion at the beginning: when THE HTML content is within the drawing ability range of canvas, plan I (version 2: BLOB method) is used; when the HTML content exceeds the drawing ability range of Canvas, Plan II is used.

Finally the enclosed demo code (including mode, canvasWidth canvasHeight, drawTimes can adjust the custom)

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas test</title> </head> <body> <button Id =" BTN "> download canvas</button> <canvas ID ="canvas"></canvas> </body> <script type="text/javascript"> Mode = 'base64' * Width * Height Canvas rendering state Base64 Length Picture downloadable or not Picture size * 1000*32700 Canvas can be rendered Canvas cannot be rendered * 2400*32700 Canvas can be rendered Base64 length 1226803 picture downloadable Picture size 1.4m * 2600*32700 Canvas can be rendered Base64 length 2084390 picture downloadable Image size: 1.6m * 2700*32700 Canvas can be rendered Base64 length: 2152518 Images cannot be downloaded * 8200*32700 Canvas can be rendered Base64 length: 6397386 images cannot be downloaded * 8300*32700 Canvas cannot be rendered * * Conclusion * 1. Canvas has a ceiling for rendering a canvas. Related factors: width, height (but there is no positive correlation with width * height), color complexity of picture * 2. For the same size canvas, adding color will increase the size of the image. == "so the width and height data tested above are not representative. Blob * @type {String} */ / var mode = 'base64'; blob * @type {String} */ / var mode = 'base64';  var mode = 'blob'; var canvasWidth = 2400 var canvasHeight = 32700; DrawTimes = 0; /** * drawTimes = 0; // var drawTimes = 10020000; var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); canvas.width = canvasWidth; canvas.height = canvasHeight; ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); Var I =0; /** * (var I =0; i<drawTimes; i++){ var colors = ['red','yellow','grey','green','blue','white'] ctx.fillStyle = colors[i%6]; ctx.fillRect(i/drawTimes*canvasWidth, Math.random()*canvasHeight, 10, 10); } if (mode === 'base64') { document.getElementById("btn").addEventListener("click", function(event) { var base64 = canvas.toDataURL(); console.log(base64.length) var link = document.createElement('a'); link.textContent = 'download image'; link.href = base64; link.download = "mypainting.jpeg"; link.click() }, false); } else if (mode === 'blob') { document.getElementById("btn").addEventListener("click", function(event) { canvas.toBlob(function(blob) { url = URL.createObjectURL(blob); console.log(url) console.log(url.length) var link = document.createElement('a'); link.textContent = 'download image'; link.href = url; link.download = "mypainting.jpeg"; link.click() // no longer need to read the blob so it's revoked URL.revokeObjectURL(url); }); }, false); } </script> </html>Copy the code