Recently a little interest on HTML 5 little game, because I feel this thing may be a front an important application of the scene, for example, now every certain holidays, like alipay and taobao or other APP may give you a push notification, then click go in is a small game, basically point to go in person, as long as it’s not too, Will play, if you just get to the user’s G spot, but also to further enhance the business, both user experience, or business development, is a very good way to improve.

In addition, the HTML5 mini game I mentioned includes WebGL, WebVR, etc., not only limited to games, but also other scenes using related technologies, such as 360° online viewing of commodity pictures. The reason why we start with small games is that small games need all kinds of technologies to do a good job. It’s easier to use the same technique for other things

After looking for materials, I found that there are many ways to do things. After looking around, I decided to start from the basics, starting from the relatively simple Canvas game. After reading some relevant articles and books, I found that although it is easy to use, it is still a little difficult to give full play to its supposed ability

Then try to write a small canvas games, recently related UI has gathered all the material, but as the saying goes To do a good job, must first sharpen his device with little experience in this respect, so in order to avoid in the process of all kinds of pit, specially and saw some related articles on pit, which I feel is the place that must pay attention to performance, and doorways, So I sort it out

userequestNextAnimationFrameDo an animation loop

SetTimeout and setInterval is not designed for continuous loop of API, so may not be able to achieve smooth animation performance, so use requestNextAnimationFrame, may need to polyfill:

const raf = window.requestAnimationFrame
  || window.webkitRequestAnimationFrame
  || window.mozRequestAnimationFrame
  || window.oRequestAnimationFrame
  || window.msRequestAnimationFrame
  || function(callback) {
    window.setTimeout(callback, 1000 / 60)}Copy the code

Use clipping areas to manipulate animated backgrounds or other immutable images

For simple animations, erasing and repainting everything on the cloth per frame is desirable, but if the background is more complex, you can use clipping region techniques to achieve better performance with fewer draws per frame

Using the clipping region technique to restore the background image occupied by the previous animation frame:

  • callcontext.save(), save the screencanvasThe state of the
  • By calling thebeginPathTo start a new path
  • incontextObjectarc(),rect()Etc to set the path
  • callcontext.clip()Method to set the current path to the screencanvasClipping area of
  • Erases the screencanvas(actually only the area where the clipping area is erased)
  • Draws the background image to the screencanvas(The draw operation actually only affects the range of the clipping region, so there are fewer pixels per frame to draw the image)
  • To restore the screencanvasTo reset the clipping region

Off-screen buffer (off-screen Canvas)

First draw to an off-screen canvas, and then draw the off-screen canvas to the main canvas by drawImage. That is, the off-screen canvas is used as a cache area. It can cache the screen data that needs to be repeatedly drawn to reduce the consumption of calling canvas API

const cacheCanvas = document.createElement('canvas')
const cacheCtx = cacheCanvas.getContext('2d')
cacheCtx.width = 200
cacheCtx.height = 200
// Draw to the main canvas
ctx.drawImage(0.0)
Copy the code

Although the off-screen canvas cannot be seen in the field of view before drawing, it is better to set its width and height to be the same as the size of the cache element, so as to avoid wasting resources and drawing unnecessary images. Meanwhile, scaling images during drawImage will also consume resources. If necessary, multiple off-screen canvas can be used. When the off-screen canvas is no longer in use, it is best to reset the reference to NULL manually to avoid that the garbage collection mechanism cannot work properly and occupy resources due to the association between JS and DOM

Make the most ofCSS

background

If you have a large static background, it may not be a good idea to draw it directly to canvas. If you can, place the large background as background-image on a DOM element (for example, a div) and place the element behind the canvas. This eliminates the need for a canvas rendering

The transform is

CSS’s Transform performance is superior to Canvas’s Transform API because the former is based on its ability to make good use of the GPU, so if possible, CSS can be used to control the transform transform

Turn off transparency

The API that creates the Canvas context takes a second argument:

canvas.getContext(contextType, contextAttributes)
Copy the code

ContextType = webGL, webGL2, bitmaprenderer, contextType = 2d, contextType = webGL, webGL2, bitmaprenderer, contextType = 2d

ContextAttributes is a contextAttributes attribute used to initialize some attributes of the context. For different contexttypes, the value of contextAttributes varies. For commonly used 2D, contextAttributes can be:

  • alpha

Boolean value indicating that canvas contains an alpha channel. The default is true. If set to false, the browser will assume that the Canvas background is always opaque, which speeds up the drawing of transparent content and images

  • willReadFrequently

Boolean value indicating whether there is a repeat read plan. GetImageData () is often used, which forces the software to use 2D Canvas and save memory (rather than hardware acceleration). This scheme is suitable for the existing attribute GFX. Canvas. WillReadFrequently environment. And set to true (by default, only B2G/Firefox OS)

Low support, currently only supported by Gecko kernel browsers, not commonly used

  • storage

String indicates which storage method to use (default: persistent).

Low support, currently only Blink kernel browser support, not commonly used

Above three properties, the commonly used alpha line, if your game use the canvas and does not need to be transparent, when using the HTMLCanvasElement. GetContext () to create a graphics context when the alpha option is set to false, This option helps the browser optimize internally

const ctx = canvas.getContext('2d', { alpha: false })
Copy the code

Try not to call time-consuming apis too often

For example,

Shadow-related apis, including shadowOffsetX, shadowOffsetY, shadowBlur, and shadowColor

Drawing related apis, such as drawImage and putImageData, can also add time to scaling while drawing

Of course, the above is to try to avoid frequent calls, or other means to control performance, must be used where necessary

Avoid floating point coordinates

When using Canvas for animation drawing, if the calculated coordinates are floating point numbers, the problem of CSS sub-pixel may occur, that is, floating point values will be automatically rounded to integers. In the process of animation, since the actual movement track of elements is not obtained strictly according to the calculation formula, You can have jitter, you can also have anti-aliasing at the edges of the elements and that can affect performance because you’re doing unnecessary forensic operations

Do not call the render render operation too often

Rendering apis, such as Stroke (), fill, and drawImage, are used to realistically draw the state in the CTX state machine onto the canvas, which is also performance expensive

For example, if you want to draw ten line segments, it is much more efficient to draw the state machine of ten antenna segments in the CTX state machine first and then draw them all at once, rather than each segment once

for (let i = 0; i < 10; i++) {
  context.beginPath()
  context.moveTo(x1[i], y1[i])
  context.lineTo(x2[i], y2[i])
  // Each line segment calls the draw operation separately, which costs performance
  context.stroke()
}

for (let i = 0; i < 10; i++) {
  context.beginPath()
  context.moveTo(x1[i], y1[i])
  context.lineTo(x2[i], y2[i])
}
// Better performance is achieved by drawing a path that contains multiple lines first and then drawing it once at the end
context.stroke()
Copy the code

Change the state machine CTX as little as possible

CTX can be regarded as a state machine. For example, fillStyle, globalAlpha, and beginPath apis all change the state of the CTX. Changing the state of the state machine frequently affects performance

For example, if you draw a few lines of text on a canvas, the font at the top and bottom is 30px, the color is yellowgreen, and the middle text is 20px pink, you can draw the top and bottom text first. Draw the middle text instead of having to draw it from the top down, because the former reduces a state machine change

const c = document.getElementById("myCanvas")
const ctx = c.getContext("2d")

ctx.font = '30 sans-serif'
ctx.fillStyle = 'yellowgreen'
ctx.fillText("Hi, I'm the top line.".0.40)

ctx.font = '20 sans-serif'
ctx.fillStyle = 'red'
ctx.fillText("Hi, I'm in the middle.".0.80)

ctx.font = '30 sans-serif'
ctx.fillStyle = 'yellowgreen'
ctx.fillText("Hi, I'm the bottom line.".0.130)
Copy the code

The following code achieves the same effect, but with less code, and better performance by changing the state machine once less than the above code

ctx.font = '30 sans-serif'
ctx.fillStyle = 'yellowgreen'
ctx.fillText("Hi, I'm the top line.".0.40)
ctx.fillText("Hi, I'm the bottom line.".0.130)

ctx.font = '20 sans-serif'
ctx.fillStyle = 'red'
ctx.fillText("Hi, I'm in the middle.".0.80)
Copy the code

Make as few calls as possiblecanvas API

Well, Canvas is also drawn by manipulating JS, but compared with normal JS operations, calling canvas API will consume more resources, so please make a good plan before drawing. It is cost-effective to reduce the calls of Canvas API through proper js native computing

Of course, keep in mind that if the cost of eliminating one line of Canvas API calls is adding ten more lines of JS computation, this might not be necessary

Avoid blocking

Some time-consuming operations, such as calculating a large amount of data, including too many drawing states in a frame, and large-scale DOM operations, may lead to page lag and affect user experience. The following two methods can be used:

web worker

The most commonly used scenario of Web workers is a large number of frequent calculations to reduce the main thread pressure. If large-scale calculations are encountered, this API can be used to share the main thread pressure. This API has good compatibility, and since canvas can be used, web workers can be fully considered

Task decomposition

Decomposing a large task into several small tasks using timer polling. To decomposing a task, the task must meet the following requirements:

  • Loop processing operations do not require synchronization
  • Data does not require sequential processing

The decomposition task includes two scenarios:

  • Allocation based on the total amount of tasks

For example, a total task of ten million operations can be broken down into ten small tasks of one million operations

// Encapsulate the timer to decompose the task function
function processArray(items, process, callback) {
  // Duplicate a number of copies
  var todo=items.concat();
  setTimeout(function(){
    process(todo.shift());
    if(todo.length>0) {
      // Use the timer again for the currently executing function itself
      setTimeout(arguments.callee, 25);
    } else{ callback(items); }},25);
}

/ / use
var items=[12.34.65.2.4.76.235.24.9.90];
function outputValue(value) {
  console.log(value);
}
processArray(items, outputValue, function(){
  console.log('Done! ');
});
Copy the code

The advantage is that the task allocation mode is simpler and more controlling, while the disadvantage is that it is difficult to determine the size of small tasks

Some small tasks may take longer than others for some reason, causing the thread to block; Some small tasks may require much less time than others, resulting in a waste of resources

  • Allocation based on running time

Run a must in addition level of operations, for example, does not directly determine how much is allocated for tasks, or distribution of the relatively small granularity, in each one or a few calculation is finished, see this section of the consumption of computing time, if the time is less than a certain critical value, such as 10 ms, then to continue operations, or pause, to wait until the next polling

function timedProcessArray(items, process, callback) {
  var todo=items.concat();
  setTimeout(function(){
    // Start the timer
    var start = +new Date(a);// if the single data processing time is less than 50ms, there is no need to decompose the task
    do {
      process(todo.shift());
    } while (todo.length && (+new Date()-start < 50));

    if(todo.length > 0) {
      setTimeout(arguments.callee, 25);
    } else{ callback(items); }}); }Copy the code

The advantage is that the problem of the first case is avoided. The disadvantage is that there is an extra time comparison operation, and the extra operation process may also affect the performance

conclusion

It seems that the canvas game I am preparing will take a long time to make. There is not much time left except for work every day, so I don’t know when it will be finished. If everything goes well, I would like to remake it with some game engines such as Egret, LayaAir and Cocos Creator. Familiarize yourself with the use of these game engines and then write a series of tutorials…

Ah, in that case, it seems to be a protracted war