Best performance
I am already using a canvas for some other things, would it be faster to just put the bar on that?
If you are already rendering to a canvas that is under the boost bar the answer is...
- Yes much faster to draw the boost to the canvas.
- No if text render quality is more important than frame rate.
Simple render function
You can either render the whole boarder, background, bar and text each frame, and still have a performance increase over HTML reflows
const boostBar = { // ? and values are unknown set them to what you need
x : ?, // For visual quality ensure these values are floored
y : ?, // ...
width : ? // ...
height : ? // ...
borderWidth : ? //...
text : "Boost",
max : ? max boost,
boost : ? current level,
font : "size and font name",
style : {
bg : "CSS style value",
boarder : "CSS style value",
bar : "CSS style value",
font : "CSS style value",
},
draw(ctx) {
const bB = boostBar; // aliases to reduce code noise
const stl = bB.style;
// set to bar local coords
ctx.setTransform(1, 0, 0, 1, bB.x, bB.y);
// Draw outer boarder
ctx.fillStyle = stl.boarder;
ctx.fillRect(0, 0, bB.width, bB.height);
// Draw inner background
const bW = bB.borderWidth;
const w = bB.width - bW *2; // will need this again
const h = bB.height - bW *2; // will need this again
ctx.fillStyle = stl.bg;
ctx.fillRect(bW, bW, w, h);
// calculate bar size
const barSize = (bB.boost / bB.max) * w;
// draw bar
ctx.fillStyle = stl.bg;
ctx.fillRect((w - barSize) / 2, bW, barSize, h);
ctx.font = bB.font;
ctx.fillStyle = stl.font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(bB.text, w / 2, bB.height / 2);
// reset transform if you render other content at absolute coords
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Note that you can use
// ctx.resetTransform(); // but check for support
}
}
There are however some problems with this. The canvas text quality is very poor compared to the DOM, and if you are rendering text elsewhere using another font, switching fonts means that the code is not optimal.
The following addresses rendering issues.
Sprite sheet
Sprites are fast. Even a medium level device can render 400+ scaled rotated, non uniformly scaled, alpha faded sprites per frame @ 60FPS if done correctly, a medium Laptop 1000+ and top end machines, well I have got 64000+ @ 60FPS
Ideally for the best performance and quality you should have the (boost bar) background and text pre-rendered. Either as a loaded image (PNG) or (SVG) that you scale and render once to an offscreen canvas. Or render them with code to the offscreen canvas.
You should use a single offscreen canvas to group related sprites. As an example using only the background and text create a canvas that is twice the height. At game start render the background in the top half and the text in the bottom.
The code to render at run time then becomes
// following on from above snippet
spriteSheet : // an offscreen canvas containing background and text
draw(ctx) {
const bB = boostBar; // aliases to reduce code noise
const stl = bB.style;
// set to bar local coords
ctx.setTransform(1, 0, 0, 1, bB.x, bB.y);
// Draw sprite that has background and boarder
ctx.drawImage(bB.spriteSheet,
0, 0, bB.width, bB.height,
0, 0, bB.width, bB.height
);
// calculate bar size
const barSize = (bB.boost / bB.max) * w;
// draw bar
ctx.fillStyle = stl.bg;
ctx.fillRect((w - barSize) / 2, bW, barSize, h);
// Draw sprite that has text overlay
ctx.drawImage(bB.spriteSheet,
0, bB.height, bB.width, bB.height,
0, 0, bB.width, bB.height
);
// reset transform if you render other content at absolute coords
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Note that you can use
// ctx.resetTransform(); // but check for support
}
Text quality & performance
Interactive full screen canvas apps can have many (100+) widgets rendered using the 2D API. The more you need the more favorably it is to render direct to canvas.
But...
Canvas text is notoriously poor quality and a good reason to take the DOM performance hit in favor of quality.
If you are already rendering text to the canvas and find it acceptable then for consistency, render all text the same way.
Sprite sheet
As outlined above pre rendering text to an offscreen canvas sprite sheet will have improvements in performance. Quality can be improved using some simple text rendering techniques.
Production render
You can render text in production using Photoshop or like software and load as a PNG
Using the above two methods has some benifits
No text at all
Text is language dependent, fonts rendering is inconstant from browser to browser, browser version, OS version, GPU setup. The best option is in fact to not use any text at all, or minimize text use in favor of icons.
Sub pixel rendering.
You can get very high quality pre-rendered text using sub pixel rendering (AKA True Type) Note however it is background color dependent and thus for the progress bar will need two sprites, one for text over background, and one for text over the bar. You then render both text sprites, clipping them to as needed.
Caveats
One frame per frame. Dont use more than one call to
requestAnimationFrame` per display frame. Multi frames calls have many problems. Render all content from one main loop function.
Changing fonts during a frame render should be minimized, this includes the font size. The best is that you use only one font, set at a fixed resolution. For example set font once at start ctx.font = "32px Arial";
and then use the transform to size it. ctx.setTransform(fontSize / 32, 0, 0, fontSize / 32, textX, textY)
Warning: Avoid the following as they are frame rate killers and can make a 60FPS visualization drop to only a few frames a second.
ctx.filter
Only for static renders
Shadows. Maybe if the blur is small and its one or two items, but it adds an order of magnitude to rendering time for each change to any shadow settings that are rendered.
Dashed lines (on Chrome) ctx.strokeText
the number of dashes have a one time performance cost. Calling ctx.stroke
with dashed line ctx.setLineDash(
, or after changing the transform while there is an active line dash, there is performance hit directly related to the number of dashes drawn (including clipped strokes not rendered).
In previous version of this answer I said "even if not rendered" referring to the performance hit of dashed strokes, that was incorrect. There is no cost until you call ctx.stroke