1. The demand

Recently, clove doctor’s product leader and double 叒 yi to do things, want to do a doctor to invite questions in H5 function, can share the doctor’s TWO-DIMENSIONAL code business card, support mobile, PC and wechat. The previous images were generated by the back end and cached for three days, resulting in delayed information updates. Doing it on the front end avoids these problems.

It is easy to say that it is not a function to save pictures, simply look at the requirements:

  • Improve the card information, when sharing information more three-dimensional
  • Edit the profile entry
  • Save picture entry
  • It can solve the problem of doctor card cache time
  • I 缟 grow the following way
image

The analysis comes down to two points

  • HTML displays real-time user information
  • Click Save to save the current page as a local image, without the function button

2. Plan

I’ve heard of a library that can convert HTML to canvas, and then canvas to images, and then images can be downloaded from…. (Development basically depends on hearing (search), this is nonsense)

HTML -> Canvas -> image -> a[download]

  1. Html2canvas. Js: Convert HTMLDOM to canvas element. portal
  2. CanvasAPI: toDataUrl() converts canvas to Base64 format
  3. Create a[Download] tag to trigger a click event for the download

3. Pit picking performance

Since the program has been decided, the following began to step on the pit performance, 👏

Principle of 3.1.

The official description is as follows:

Js will iterate over the DOM node where the page is loaded, gather information about all elements, and then use that information to render the page. In other words, the library doesn’t actually take screenshots of the page, but rather draws the canvas piece by piece based on elements and attributes read from the DOM. As a result, it can only properly render elements and attributes that it understands, which means that there are many CSS attributes that don’t work.

/ / v0.4.1
html2canvas(element, {
    onrendered: function(canvas) {
        // Now you have the Canvas DOM element}});/ / v0.5.0
html2canvas(element, options).then(canvas= > {
    // Now you have the Canvas DOM element
});Copy the code

So you can probably guess what the workflow should look like:

  1. Recursively process each node, noting how it should be drawn. (Div draws border and background, text draws text, etc.)
  2. Consider the hierarchy of nodes. For example, many layer-related style attributes: z-index, float, position, etc.
  3. Start at a low level and work your way up to the canvas. High level overlays low level (much like the browser’s own rendering process).

3.2 the pit 💀

The official version is V0.4.1-7.9.2013, and the latest version is V0.5.0-Beta4. For our development, if we are not playing with new features or something, we will usually choose the official version. The result is that the first pit fell into the climb for a long time.

3.2.1 Image Blur

During the development, chrome emulator was used to generate canvas and no blur was found. However, when PC agent mobile phone was used to request development resources, I found a very obvious sense of blur in the picture.

As shown in figure:

image

It is easy to imagine that the problem may be the calculation of pixel density on the mobile end.

Device pixel ratio (DPR for short) defines the corresponding relationship between physical pixels and device independent pixels, and its value can be obtained according to the following formula:

Device pixel ratio = physical pixels/device-independent pixels // in a certain direction, x or Y

Knowing this doesn’t help because there is no place in the documentation where you can configure the pixel ratio.

However, through research, it is found that the official document is actually 0.4.1. Since version 0.5.0, custom Canvas has been secretly introduced as a configuration item, and it will start drawing based on the canvas we passed in. Therefore, when we call HTML2Canvas, we can first create a canvas of appropriate size and then pass it in.

Without further ado, first upgrade the library to 0.5.0, and then:

/** * Get the pixel ratio according to window.devicepixelRatio */
function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio;
    }
    return 1;
}
/** * converts the passed value to an integer */
function parseValue(value) {
    return parseInt(value, 10);
};
/** * draw canvas */
async function drawCanvas(selector) {
    // Get the DOM node you want to convert
    const dom = document.querySelector(selector);
    const box = window.getComputedStyle(dom);
    // DOM node width after calculation
    const width = parseValue(box.width);
    const height = parseValue(box.height);
    // Get the pixel ratio
    const scaleBy = DPR();
    // Create a custom canvas element
    const canvas = document.createElement('canvas');

    // Set canvas element property width to DOM node width * pixel ratio
    canvas.width = width * scaleBy;
    canvas.height = height * scaleBy;
    // Set canvas CSS width to DOM node width
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    // Get the brush
    const context = canvas.getContext('2d');

    // Enlarge all drawn content by pixel ratio
    context.scale(scaleBy, scaleBy);

    // Pass the custom canvas as a configuration item and start drawing
    return await html2canvas(dom, {canvas});
}Copy the code

The above code first gets the device pixel ratio and creates a larger canvas based on the ratio. For example, 2 times screen is 2 times, 3 times screen is 3 times, 8 times mirror is 8 times… Mobile screen shots have the same effect as HTML display, and basically you can’t see the difference.

image

3.2.2 Why is the picture missing

PC screenshot:

image

There could be a number of reasons, but after checking it out, it turns out that it’s because the image in the canvas has crossed domains. Here’s the explanation. In short, it’s: Cross-domain pictures can be drawn in canvas, but the canvas is in the “polluted” state, and the polluted canvas will have problems using API such as toDataUrl().

So, now we need to do two things:

  1. Set the img elementcrossOriginProperty with a value ofanonymous
  2. Image server Settings allow cross-domain (return CORS header)

The first thing is easy because HTML2Canvas itself supports useCORS: true;

But the second thing depends. When the image is on your server, it’s just a matter of asking the backend guy to change the configuration. But when an image is placed on a CDN… well, for faster response, many CDNS will cache the image’s return value without the CORS header. Js requests are intercepted because there is no CORS header. At this point, we can use server forwarding with CORS header. (It is a good solution to put a node middle layer in front to carry out server forwarding, which will be discussed separately next time)

OK. Using the above scheme, let’s test it out.

PC side open, perfect.

Wechat terminal, oh, still not good. Later, it was found that there was no problem using HTML2Canvas 0.5.0, but using 0.4.1 to draw canvas during development would still lead to picture loss. The guess is that HTML2Canvas has something indescribable in preloading and drawing pictures. To solve this problem, we used a very violent solution: get the image with JS, get its base64, put it back in the SRC of IMG and draw it.

/**
 * 图片转base64格式
 */
img2base64(url, crossOrigin) {
    return new Promise(resolve= > {
        const img = new Image();

        img.onload = (a)= > {
            const c = document.createElement('canvas');

            c.width = img.naturalWidth;
            c.height = img.naturalHeight;

            const cxt = c.getContext('2d');

            cxt.drawImage(img, 0.0);
            // Get the base64 encoded data of the image
            resolve(c.toDataURL('image/png'));
        };

        crossOrigin && img.setAttribute('crossOrigin', crossOrigin);
        img.src = url;
    });
}Copy the code

The hole had finally stumbled through.

3.2.3 chamfering

Border-radius must be less than or equal to half the length of the short side, and must be a specific value, otherwise the effect can be magical.

Using a 0.5px border with a pseudo-element can also work wonders, so use the border attribute instead

In 0.4.1, a circular image can only be set as a background image. Img does not support border-radius drawing. In 0.5.0, there is no such restriction

3.2.4 dotted line

As mentioned earlier, HTML2Canvas does not support all CSS properties. > > < span style = “font-size: 16px! Important; Sectionmap works on PC, but in wechat, SecurityError may be reported when you try to render dashed lines using sectionmap. The operation is insecure. Error, resulting in base64 transfer failure

3.3 save

Ideal:

@param {String} data image data to be saved locally * @param {String} filename filename */
saveFile(data, filename) {
    const save_link = document.createElementNS('http://www.w3.org/1999/xhtml'.'a');
    save_link.href = data;
    save_link.download = filename;

    const event = document.createEvent('MouseEvents');
    event.initMouseEvent('click'.true.false.window.0.0.0.0.0.false.false.false.false.0.null);
    save_link.dispatchEvent(event);
}Copy the code

Reality:

PC: Perfect. Wechat big guy: Excuse me, what did you say? I can’t hear you? !

Well, there is no response on wechat at all. After checking wechat SDK, I found:

  • downloadImageOnly supportuploadImageInterface uploads images.
  • uploadImageInterfaces only supportchooseImageInterface album selected pictures.
  • chooseImageInterface to select images from local albums.
  • So the question is, what else do we need to do when the pictures are in the album?
  • .

4. Deliver (in pain and compromise)

The final solution is as follows:

  • The page is displayed
  • Obtain all information of the current user, such as profile picture and TWO-DIMENSIONAL code
  • Convert all images to base64
  • Apply colours to a drawinghtml
  • drawcanvas
  • Save canvas as base64
  • replacehtmlimg.srcFor base64
  • After converting the page to the picture, users in wechat can long press the page to call up the actionSheet to identify or save the picture

That is, when the user first enters the page, HTML is displayed. After the js execution, delete the original HTML and replace it with a picture.

Back to our requirements:

  • HTML displays real-time user information
  • Click Save to save the current page as a picture to local

In fact, only the first point is finally realized, and the second point is actually achieved half, although the picture is generated, but the saving function still requires the user to long press the picture, adjust the wechat built-in menu to complete. Product managers and programmers alike need to know as much as possible about problems and limitations that may not have been previously considered once wechat is taken into account during H5 development. Know what you can and can’t do in wechat to reduce the cost of development and repeated communication.

Hope the above content can be helpful to everyone’s future development.

Author: DINGxiang YUAN F2E – Gu Chongxi