I. Requirements in the project are as follows:

Download the pictures generated by the business daily to the user’s mobile phone for saving

Two. Step pit train of thought:

  1. First of all, because I used a third-party app (Dingding) for development with webView embedded in it, I couldn’t get the API of screenshot (besides, the daily paper needed to be generated was longer than one screen, and screenshot was also troublesome).
  2. Therefore, it is natural to use the third-party tool canvas2HTML to convert the DOM of the specified scope in the page to canvas
  3. Then use the Canvas APItoDataUrlGet image data in Base64 format
  4. Try downloading directly with the A tag at this point
`<a href="base64Url" download="name.jpg"></a>`
Copy the code
  1. It has been proved that this method is invalid on mobile terminal, indicating that image download fails, because mobile terminal cannot directly download base64 format image (it is said that on PC terminal, Chrome can directly download, and other browsers seem to have compatible writing method, you can verify by yourself)
  2. Now you can only transfer the image to the back end for saving and then download it using the server address
  3. To facilitate back-end processing, instead of directly uploading base64 data, we will first convert Base64 to BLOB, then simulate a form object, put the BLOB in it, and submit it to the backend using POST
  4. After getting the server address, try the A tag download again
  5. There are two cases here. If the picture address and the project have the same origin, according to the online saying, clicking a label should be able to download directly, but it has not been verified here
  6. Here, the image is saved to a non-same-origin server, so after clicking the A label, it cannot be downloaded automatically. Instead, it will switch to the default browser to open the image, and then you can long press to download. The experience here is not good
  7. Therefore, the a label scheme was finally abandoned and a pop-up layer was added to display the picture and prompt users to long press to download. Thus, this function was perfectly realized

Iii. Implementation process:

Html2canvas converts pages to Canvas -> Canvas converts dataURL -> base64ToBlob -> new FormData, Place the BLOB into the form object -> Send the POST request to the backend to save the image -> return the image address to the backend -> Display the image using the online address -> Hold the user to save the image

Iv. Step by step:

4.1 html2canvas
NPM install html2canvas --save // import html2canvas from'html2canvas'; Vue.prototype.html2canvas = Html2canvas; / / use this html2canvas (document. QuerySelector ('#id'))
  .then((canvas) => {
    // todo...
  })
Copy the code
4.2 Canvas converts dataURL
let dataUrl = canvas.toDataURL('image/jpeg');
Copy the code
4.3 base64ToBlob
/** * base64 to blob * @param {String} code Base64 number data * @return {undefined}
 * @author xxx
 */
base64ToBlob (code) {
  let parts = code.split('; base64,');
  let contentType = parts[0].split(':') [1];let raw = window.atob(parts[1]);
  let rawLength = raw.length;
  let uInt8Array = new Uint8Array(rawLength);
  for (let i = 0; i < rawLength; ++i) {
    uInt8Array[i] = raw.charCodeAt(i);
  }
  return new window.Blob([uInt8Array], {type: contentType, name: 'file_' + new Date().getTime() + '.jpg'});
}

let blob = base64ToBlob(dataUrl);
Copy the code
4.3 Simulate formData and submit it
New formData / /letformData = new FormData(); // Store blob to formData.append('file', blob);
Copy the code

Here we use axios because we are using VUE, so pay attention to the content-type when submitting

Note: when creating a request, you need to create a new instance of axios to request it. Do not use the imported AXIos, otherwise the content-Type modification will fail

import axios from 'axios'; // Create a new axios instanceletinstance = axios.create({}); // Set content-type tofalseWe have to do is the instance. The defaults. Headers. Post ['Content-Type'] = false; // When creating a request, use the newly created instance instead of using the original axios instance({method:'POST',
  data: formData
})
  .then(res => {
    // todo...
  })
Copy the code
4.4 Display an image using the returned server address and hold down the image to save it

There is no need to say anything here, just create a mask layer to implement

Five. Optimization point

  1. Because canvas2HTML -> requires a lot of steps to initiate a request, and if the DOM conversion is complex, it will take a long time to process events in the middle. Therefore, loading processing should be done properly during this period. Otherwise, when the user clicks on it, they might see a window of about 1 second, and the page doesn’t tell them anything
  2. The button to generate pictures should be limited to continuous clicking to avoid frequent triggering by users

Above, the function is all realized, although it can not achieve click direct download, but also within the scope of conditions allow, to achieve a better user experience


Finally, let’s talk about the situation on ios

This scheme has been tested smoothly on Android, but there have been some doubts on ios

The main problem is that canvas.todataURL () is not working

At that time, Android was successfully adjusted. When testing on ios, it was found that JS stopped running at Canvas.todataURL (), with no return value and no error message

Finally, it was found that the watermark effect added by myself caused the execution in ioscanvas.toDataUrl()When there is no response (canvas draws the watermark, converts base64, and adds it to the implementation mode of parent backgroung-Image), the implementation mode of watermark will work normally after changing

Here is the whole day to step on the pit process to write down for your reference:

At first, I suspected that there was something wrong with the canvas transformed from HTML2Canvas. I read a lot of questions online, including the issue of html2Canvas on Github. Some people said that the problem might be the html2Canvas version, and some said that the picture drawn by canvas was too large. The various statements that caused canvas.todataURL () to run on ios to be forcibly blocked by the system failed to solve the current problem after testing

Although it turned out not to be the above problem, I still wrote down the test results

1. Html2canvas version problem:

NPM installs the alpha version of “html2Canvas “: “^1.0.0-alpha.12” by default

Then I tested the last official release version, v0.4.1, confirmed that can run normally, but met cannot convert more than part of the screen the dom, and transformation of image fuzzy problems to spend too much energy to do, and the author said in the old version there are too many bugs, it is recommended to use the new version, so finally gave up the old version

2. The picture drawn by canvas is too large, resulting incanvas.toDataUrl()Running on ios is forcibly blocked by the system

The size of my converted image is around 200K (using the type ‘image/ JPEG ‘), but I can’t find out what the limit is online

At that time, I used my newly built canvas to compress the picture again. When it was compressed to only 1KB, it ran canvas.todataURL () normally, so this problem was basically eliminated