1. What is Canvas?

Canvas is defined in MDN like this:

< Canvas > is a new element in HTML5 that can be used to draw graphics using scripts in JavaScript. For example, it can be used to draw graphics, make photos, create animations, and even do real-time video processing or rendering.

Canvas is a drawable area defined by HTML code with height and width attributes. JavaScript code can access this area, similar to other generic TWO-DIMENSIONAL apis, with a full set of drawing functions to dynamically generate graphics.

Wechat applet supports canvas starting from base library 1.0.0, and supports a new Canvas 2D interface (type attribute needs to be specified) starting from 2.9.0. Meanwhile, it supports same-layer rendering, and the original interface is no longer maintained.

Wechat applet supports Canvas from the very beginning, but there are many deficiencies in the early canvas. The problem of canvas’s high level covering other components has always been criticized. In 2.9.0, applets released a new Canvas 2D interface, which can support the same layer rendering, and solved this “big problem”.

2. Canvas application scenario of small program

2.1 Drawing posters

At present, the following two methods are generally used to generate activity sharing posters in small programs:

  • Server compositing: returns directly to the front-end image URL
  • Client-side composition: The client draws using canvas

In current business scenarios, client-side composition is superior to server-side composition to avoid unnecessary WASTE of CPU and bandwidth. And the backend dad will set the facts and reason to reject this requirement, server synthesis, no way!


2.2 Drawing Animation

At this stage in the small program simple animation drawing commonly used schemes are mainly the following four:

Animation types Realize the principle of Defects in
CSS animations useThe CSS gradientandCSS animationsTo create simple interface animations The real plane comes in occasionallyflashingandjitterThe phenomenon of
wx.createAnimation usewx.createAnimationInterface to dynamically create simple animation effects Performance is not good, there is a lag, ios model page occasionally appearflashingThe phenomenon of
Keyframe animation usethis.animateCreate keyframes to animate, with better performance and more controlled interfaces Ios model page occasionally appearsflashingThe phenomenon of
GIF animation Animation will generate GIF files, using small proceduresimageorcover-imageThe label to show Show up on the real thingThe sawtoothandWhite laceThe situation attracts criticism

The above four schemes can only achieve simple animation drawing, and occasionally flicker and shake on ios real phone. Canvas, on the other hand, draws graphics through JavaScript script, which is more stable and can cover complex animation logic, such as simulation of rotary table lottery, animation like in live broadcast room, scratch-off and other effects.


3. Canvas Pit tour

In a word, Canvas occupies a place in the development of small programs in wechat, but there are also many “pits” that have to be filled. At present, there is no similar article system to record these problems. This paper mainly records the problems and solutions encountered in the development practice of Canvas.

3.1 The highest level of canvas drawing of small program?

It is well known that there is a special class of built-in components in applets – native components, which are different from WebView rendered built-in components and are rendered by native clients. As a supplement to Webview, native components bring richer features and higher performance to small programs, but at the same time, due to the separation from Webview rendering also brought developers no small trouble, here to understand the use of native components.

The Canvas API supported by the apl 1.0.0 is the native component. The level of the native component is always the highest, and it is not controlled by the Z-index property and cannot be overwritten with view, image and other built-in components. Therefore, canvas drawing is usually at the top level, and problems may arise in the actual development process. As shown in the figure below, the thumbs-up animation and shopping bag animation are drawn by canvas. When opening the popup of commodity list, these two animations will reveal:


  • At first, I came up with the solution of listening to the opening event of the popup of the goods list. When the popup opens, I move the animation of “like” and “shopping bag” to the outside of the screen, and when the popup closes, I move it to the inside of the screen. This method cures the root rather than the root cause. With the continuous “addition” of PM, more and more component states need to be maintained. Each additional component needs to consider the issue of Canvas level coverage.
  • In the second method, the use of native components such as cover-view and cover-image can alleviate the problem of hierarchical coverage to a certain extent. However, excessive use of native components will lead to difficult maintenance of the hierarchy and more bugs in subsequent iterations.
  • The third way is to usecanvasToTempFilePathTemporarily convert the Canvas to an image, then hide the canvas and display tempImage. This method is suitable for static canvas drawing. As for canvas animation, it is refreshed every 16ms, and converting canvas into pictures will greatly affect performance.

Fortunately, the applets development team recognized the limitations of native components and implemented a refactoring of the applets native component to introduce “same-layer rendering.” Want to further understand the principle of the same layer rendering, you can refer to this article – “small program of the same layer rendering principle analysis”.

Applets base 2.9.0 supports a new Canvas 2D interface (with the type attribute specified) and same-layer rendering. Therefore, for Canvas development, the most effective way to solve the problem of hierarchical coverage is to transform the old API into the new Canvas 2D API.

3.2 Why cannot the Font be bold?

Some people in wechat open community asked why I made the following Settings, which can be bold on the emulator, but it does not work on android.

this.ctx.font = '700 14px normal';
Copy the code

This line of code has two problems:

  • Font must contain the font size and font family nameFont family name is missing.
  • The font-weight CSS property specifies the size of the font. Some fonts provide only normal and bold values.Bold for safetybold.

Therefore, the code has been optimized as follows:

this.ctx.font = '${fontWeight >= 700 || fontWeight === 'bold'? 'bold':'normal'} ${fontSize}px ${fontFamily || 'sans-serif'} ';
Copy the code

3.3 Why Canvas cannot draw base64 pictures?

In the business scenario of poster drawing, the sun code or TWO-DIMENSIONAL code requires the user to provide some parameters, and the server generates pictures and returns them to the front end. Generally, the URL of the picture is not returned, but the picture is base64 transcoding and returned to the front end. However, canvas user drawing’s API-drawImage does not recognize base64 format.

Here are the solutions:

  1. usewx.base64ToArrayBufferConverts base64 data to ArrayBuffer data.
  2. useFileSystemManager.writeFileWrites the ArrayBuffer data to a binary image file for the local user’s path.
  3. The image file path is inwx.env.USER_DATA_PATH,wx.getImageInfoThe interface correctly retrieves the image resource and draws the image to the canvas.
const fsm = wx.getFileSystemManager();
const FILE_BASE_NAME = 'tmp_base64src';

const base64src = function(base64data) {
  return new Promise((resolve, reject) = > {
    // Remember to remove base64 headers when writing files
    const [, format, bodyData] = /data:image\/(\w+); base64,(.*)/.exec(base64data) || [];
    if(! format) { reject(new Error('ERROR_BASE64SRC_PARSE'));
    }
    const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
    const buffer = wx.base64ToArrayBuffer(bodyData);
    fsm.writeFile({
      filePath,
      data: buffer,
      encoding: 'binary'.success() {
        resolve(filePath);
      },
      fail() {
        reject(new Error('ERROR_BASE64SRC_WRITE')); }}); }); };export default base64src;
Copy the code

3.4 Small program Canvas can’t draw network pictures?

The canvas. DrawImage applet does not support network images, only local images. Therefore, any network image needs to be cached locally and then drawn by calling the stored local resource through drawImage. Caching can be achieved through wx.getImageInfo and wx.downloadFile.

  • usewx.getImageInfoGet the temporary path to the image
const ctx = wx.createCanvasContext('myCanvas'); // Get the canvas canvas object
wx.getImageInfo({
  src: 'https://******.com/example.png'.// Network image path
  success: res= > {
    const path = res.path; // Image temporary local path
    ctx.drawImage(path, 0.0.100.100); // Draw paths on the canvas
    ctx.draw(true); }});Copy the code
  • usewx.downloadFileGet the temporary path to the image
const ctx = wx.createCanvasContext('myCanvas'); // Get the canvas canvas object
wx.downloadFile({
  url: 'https://******.com/example.png'.// Network path
  success: res= > {
    const path = res.tempFilePath; // Temporary local path
    ctx.drawImage(path, 0.0.100.100); // Draw paths on the canvas
    ctx.draw(true); / / to draw}});Copy the code

However, some people ask why I can draw pictures on the IDE and not on the real machine. This is because the wechat security domain name problem, you need to set the network picture domain name in the small program background > Settings > Server domain name > downloadFile legal domain name. Ps. Because the domain name requirements are HTTPS, and can only be changed five times a month, it is recommended to download the network image on your HTTPS server, and then go through the CDN.

I guess you’ll also be asked why a secure domain name still doesn’t display graphics on the real phone. This takes into account the loading time of the image. If you start drawing before the image is loaded, an error will be reported. Can use image bindload events or downloadTask. OnProgressUpdate to monitor image loading process.

As of base library 2.7.0, the aptlet released Canvas.createImage(), which can be used to load web images. The usage method is as follows:

const tempImgae = canvas.createImage();
tempImage.src = 'https://******.com/example.png';
tempImage.onload = () = > {
    // After the image is loaded, you can manipulate the tempImage at will
};
Copy the code

3.5 How can I Wrap Multiple Lines of Text?

Compared with CSS, SVG and Canvas have weak support for text typesetting. CSS is a word-break problem, canvas is not.

CanvasContext. MeasureText (string text) for measuring the text size information. Currently only text width is returned.

Canvas word wrap. The principle is that CanvasContext measureText (string text) this API, can return to a TextMetrics object, which contains the current context environment text double precision of width, We can then calculate exactly where line breaks should be made by accumulating each character width.

The reference code is as follows:

  wrapText(
    ctx,
    text: string,
    x: number,
    y: number,
    maxWidth = 300,
    lineHeight = 16.) {
    // Delimit characters into arrays
    const arrText = text.split(' ');
    let line = ' ';

    for (let n = 0; n < arrText.length; n++) {
      const testLine = line + arrText[n];
      const metrics = ctx.measureText(testLine);
      const testWidth = metrics.width;
      if (testWidth > maxWidth && n > 0) {
        ctx.fillText(line, x, y);
        line = arrText[n];
        y += lineHeight;
      } else {
        line = testLine;
      }
    }
    ctx.fillText(line, x, y);
  }
Copy the code

Of course, in actual business scenarios, more requirements are to realize the function of fixed-line text wrapping and overflow ellipsis. The implementation principle is the same, which will not be stated here.

3.6 How to Arrange Characters in Numeric Values?

The text is vertically aligned and can be rotated 90deg with context.rotate() in English, but this is not applicable in Chinese. In CSS, we can use writing-mode to change the direction of the document flow to achieve vertical text. Canvas implementation requires mixed calculation and word-for-word arrangement. The calculation rules are as follows: vertical arrangement of full-corner characters and rotation arrangement of half-corner characters such as English numbers.

The reference code is as follows, derived from Zhang Xinxu – Canvas text drawing automatic wrapping, word spacing, vertical line, etc. :

fillTextVertical(text, x, y) {
    var context = this;
    var canvas = context.canvas;

    var arrText = text.split(' ');
    var arrWidth = arrText.map((letter) = > {
        return context.measureText(letter).width;
    });

    var align = context.textAlign;
    var baseline = context.textBaseline;

    if (align == 'left') {
        x = x + Math.max.apply(null, arrWidth) / 2;
    } else if (align == 'right') {
        x = x - Math.max.apply(null, arrWidth) / 2;
    }
    if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
        y = y - arrWidth[0] / 2;
    } else if (baseline == 'top' || baseline == 'hanging') {
        y = y + arrWidth[0] / 2;
    }

    context.textAlign = 'center';
    context.textBaseline = 'middle';

    // Start drawing word for word
    arrText.forEach((letter, index) = > {
        // Determine the ordinate position of the next character
        var letterWidth = arrWidth[index];
        // Whether rotation is required
        var code = letter.charCodeAt(0);
        if (code <= 256) {
            context.translate(x, y);
            // English characters, rotate 90°
            context.rotate(90 * Math.PI / 180);
            context.translate(-x, -y);
        } else if (index > 0 && text.charCodeAt(index - 1) < 256) {
            / / y
            y = y + arrWidth[index - 1] / 2;
        }
        context.fillText(letter, x, y);
        // Rotate the coordinate system back to its original state
        context.setTransform(1.0.0.1.0.0);
        // Determine the ordinate position of the next character
        var letterWidth = arrWidth[index];
        y = y + letterWidth;
    });
    // Restore the horizontal and vertical alignment
    context.textAlign = align;
    context.textBaseline = baseline;
};
Copy the code

3.7 How does canvas draw rounded Rectangles?

Wechat mini program allows to draw rounded corners for ordinary elements through the setting of border-radius. However, sometimes when drawing with Canvas, rounded corners are also required, but Canvas does not provide KPI for drawing rounded rectangle. In this case, “curve saving the country” is required. First, take a look at the circle drawing API:

CanvasContext.arc(number x, number y, number r, number sAngle, number eAngle, boolean counterclockwise)

  • Number x is the x-coordinate of the center of the circle,
  • Number y represents the y coordinate of the center of the circle
  • Number r is the radius of the circle
  • Number sAngle indicates initial radian, unit radian (at 3 o ‘clock)
  • Number eAngle End radian
  • Boolean Counterclockwise if the direction of radians is counterclockwise

Therefore, we can draw four arcs first, and use the closePath method to connect the characteristics of paths to draw rounded rectangles. The encapsulation function is as follows:

const drawRoundedRect = (ctx, width, height, radius, type='fill') = > {
  ctx.moveTo(0, radius);
  ctx.beginPath();
  ctx.arc(radius, radius, radius, Math.PI, 1.5 * Math.PI);
  ctx.arc(width - radius, radius, radius, 1.5 * Math.PI, 2 * Math.PI);
  ctx.arc(width - radius, height - radius, radius, 0.0.5 * Math.PI);
  ctx.arc(radius, height - radius, radius, 0.5 * Math.PI, Math.PI);
  ctx.closePath();
  ctx[method]();
};
Copy the code

Of course, it’s not enough just to draw rounded rectangles. The actual business requirement is to add rounded corners to the image. Here, you need the following API:

CanvasContext.createPattern(string image, string repetition)

  • A method that creates a pattern on a specified image that repeats the meta-image in the specified direction
const drawRoundRectImage = (ctx, x, y, width, height, radius, image) = > {
    The diameter of the circle must be less than the width and height of the rectangle
    if (2 * radius > width || 2 * radius > height) {
      return false;
    }
    // Create an image texture
    const pattern = ctx.createPattern(obj, "no-repeat");
    cxt.save();
    cxt.translate(x, y);
    // Draw the edges of the rounded rectangle
    this.drawRoundedRect(ctx, width, height, radius);
    cxt.fillStyle = pattern;
    cxt.fill();
    cxt.restore();
  }
Copy the code

3.8 Canvas drawing blur problem

Recently, due to the need of business development, I contacted Canvas Animation, and found that the “like” icon drawn was abnormally fuzzy during development, as shown in the picture below. The left picture is the icon drawn during initial development, and the right picture is the icon drawn after fixing this problem, with a qualitative leap in clarity.


3.8.1 Why is it Vague?

There is one in the browser’s Window variabledevicePixelRatioProperty, which determines how many pixels the browser will use to render a pixel. For example, suppose the devicePixelRatio of a screen is 2, a 100×100 pixel image, under this screen, a pixel of the image will be rendered with the width of 2 pixels. So the image actually takes up 200×200 pixels on the screen, which is the equivalent of doubling the size of the image and making it blurry.

As you can see from the figure above, an HD screen has more physical pixels for the same size of logical pixels. On a normal screen, one logical pixel corresponds to one physical pixel, while on a hd screen with DPR = 2, one logical pixel consists of four physical pixels.

I believe that all those who have been familiar with Canvas drawing know that what Canvas draws is a bitmap, which is also called pixel map or raster map. Bitmap stores and displays images by recording the color, depth and other information of each point in the image. Figuratively, a bitmap can be thought of as a giant jigsaw puzzle with countless pieces, each of which represents a single solid-colored pixel. In theory, every bitmap pixel corresponds to every physical pixel. But what if you look at a picture on a high-definition screen, like Apple’s Retina display?

Suppose we have the following code, which will be displayed on the retina display of the iphoneX(Dpr=3) :

<canvas width="320" height="150" style="width: 320px; height: 150px"></canvas>
Copy the code

Where, width and height in style respectively represent the width and height occupied by the canvas element on the interface, that is, the width and height of the style. Width and height in the attribute represent the width and height of the actual canvas pixel.

The physical pixel of iphoneX itself is 1125 _ 2436, while the device independent pixel is 375 _ 812, which means that 1 CSS pixel is actually composed of 9 physical pixels. The pixel of Canvas is 320 _ 150, and its CSS pixel is 320 _ 150. In this way, under the Retina screen, 1 Canvas pixel (or 1 bitmap pixel) will fill 9 physical pixels. Since a single bitmap pixel cannot be further divided, So only the nearest color, resulting in blurred images.


The image above shows how the bitmap is filled in with retina display. The image on the left shows the normal screen with 4 bitmap pixels and the hd screen on the right has 16 pixels. The color changes because the pixels are not cut.

3.8.2 How to Solve the Drawing Ambiguity Problem?

When you understand the cause of the problem, it’s easy to solve it. For Retina adaptation, the key is to make a Canvas pixel equal to a physical pixel.

The reference code is as follows:

this.ctx = canvas.getContext('2d');
// Get the device pixel ratio for retina display
const dpr = wx.getSystemInfoSync().pixelRatio;
// Expand the canvas pixel according to the device pixel ratio, so that 1 Canvas pixel is equal to 1 physical pixel
canvas.width = this.realWidth * dpr;
canvas.height = this.realHeight * dpr;
// As the canvas expands, the canvas coordinate system also expands. If the original coordinate system is used, the drawing content will shrink
// So you need to scale it up
this.ctx.scale(dpr, dpr);
Copy the code

3.9 Canvas Animation Draws too much on some iPhone models to clear the Canvas?


Recently received a request to optimize the pendant and shopping bag animation as shown below. The original version used GIF images from the designers, but the white edge and serrations of GIF images on real machines were “a bit of a problem”. In the design of the big people “coerced lure”, only animation can be considered. Wx. CreateAnimation and this.animate do not have access to animation cycles on some iPhone models. Sometimes iPhone models cannot capture this time and will execute the animation for 1s or less, resulting in a “flicker” effect.

All in all, Canvas animation is the best practice. However, the canvas 2D API of the applet also has some disadvantages, such as automatically empties the canvas when too many pictures are drawn. As you can see in the picture below, the canvas is suddenly empty at the eighth second of the countdown animation. The pendant on the left experienced the same problem, with the canvas suddenly emptying.


There are also many similar problems on the Internet, such as “ios repeatedly jumping to a page and drawing with Canvas will lead to insufficient running memory or unexpected exit”, “Canvas 2D will not be displayed on the real computer, and there is no problem on the development tool?” . To summarize, drawing canvas too often on ios models can cause the canvas to empty and the applet to crash.

After investigating this problem for a long time, it was concluded that one reason might be that the countdown text was refreshed during the animation execution, which led to the need to redraw the picture. The time interval between the two drawings was too short, which led to the program crash and the canvas was emptied. The optimization method is as follows:

  • The text is not drawn on canvas, which only draws hanging pictures. The text uses labels and is placed on the canvas with CSS layout.
  • Add bottom pocket strategy, place a static pendant image under canvas canvas, if canvas suddenly empty, display the static image underneath. It is important to note that the bottom image should be zoomed out to make sure that the bottom image is not visible when the pendant is animated.

3.10 Canvas 2D FAQ & Tips for avoiding holes

  • Canvas 2D does not support debugging on real machine, please use preview on real machine directly.
  • Canvas 2D on the IDE behaves the same as a native component and still “seeps out”. Need to see the actual effect on the real machine.
  • The canvas tag defaults to 300px width and 150px height. Remember to explicitly set the width and height of the Canvas tag while developing.
  • Avoid setting the width and height too large, which can cause crash problems under Android.
  • Canvas-id in the same page cannot be repeated. If you use an existing canvas-ID, the canvas corresponding to this canvas tag will be hidden and will not work properly.
  • Canvas 2D has a 4096 size limit on its canvas, unlike the old Canvas.
  • Canvas 2D same-layer rendering is invalid in Pixel 3, as the wechat version of foreign channels does not support same-layer rendering.

4 the last

Due to the need of business development, the author contacted Canvas development for two months and summarized some problems encountered in sharing practice. Because of the business level is limited, if there is discrepancy on some problems, please criticize!

And last but not least, welcome to the discussion. Give it a “like” before you go. ◕ ‿ ◕. ~