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
userequestNextAnimationFrame
Do 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:
- call
context.save()
, save the screencanvas
The state of the - By calling the
beginPath
To start a new path - in
context
Objectarc()
,rect()
Etc to set the path - call
context.clip()
Method to set the current path to the screencanvas
Clipping area of - Erases the screen
canvas
(actually only the area where the clipping area is erased) - Draws the background image to the screen
canvas
(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 screen
canvas
To 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