Splashnology Magazine

How to create a Progress bar with HTML5 Canvas

How to create a Progress bar with HTML5 Canvas

There is a great amount of articles about Canvas on the internet, but I would like to show the example of how you can use Canvas on practice, namely the creation of animated progress bar.

Advertisement

Those who are impatient can find the result of the experiment here, as well as a screenshot of the final result:

The progress bar has turned out pretty simple, but at the same time, in the style of Web 2.0 – round (of course!), with elements of depth and volume: that is, shadows and gradients.

Substrate

Let’s start with a substrate, which will be filled with progress. We are going to round the angles of the rectangle with the help of arc () function, so in this case the rectangle will always be rounded at the angles in a way of semicircles. To create an arbitrary rounding (as when using a border-radius) it is sufficient to use two Bezier curves (Bezier and quadratic curves) and direct interval between them (degenerating to a point in a semicircle, as in our case). Thus, the function of creation of the substrate’s filled outline will look in the following way:

[php]function roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width – radius, y);
ctx.arc(x+width-radius, y+radius, radius, -Math.PI/2, Math.PI/2, false);
ctx.lineTo(x + radius, y + height);
ctx.arc(x+radius, y+radius, radius, Math.PI/2, 3*Math.PI/2, false);
ctx.closePath();
ctx.fill();
}
[/php]

Then we add shadows and volume to the substrate. Create volume using a linear gradient with three stops, the shadows – with the standard properties of the context:

[php]function progressLayerRect(ctx, x, y, width, height, radius) {
ctx.save();
// Define the shadows
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 5;
ctx.shadowColor = ‘#666′;

// first grey layer
ctx.fillStyle = ‘rgba(189,189,189,1)’;
roundRect(ctx, x, y, width, height, radius);

// second layer with gradient
// remove the shadow
ctx.shadowColor = ‘rgba(0,0,0,0)’;
var lingrad = ctx.createLinearGradient(0,y+height,0,0);
lingrad.addColorStop(0, ‘rgba(255,255,255, 0.1)’);
lingrad.addColorStop(0.4, ‘rgba(255,255,255, 0.7)’);
lingrad.addColorStop(1, ‘rgba(255,255,255,0.4)’);
ctx.fillStyle = lingrad;
roundRect(ctx, x, y, width, height, radius);

ctx.restore();
}[/php]

Two layers are being used here – the lower solid gray and the top white with gradients of transparency. In my opinion it provides more precise boundaries of the substrate and looks more realistic, in any case you can always experiment with all the color settings.
If everything was done correctly, you will get something like this:

Progress bar

Now let’s move to the creation of progress that will fill the substrate. Progress – it’s the same rectangle with rounded angles, it only has a different color.
But it has a special feature that differs it from the substrate and which should be taken into account in drawing – when the width of the progress is less than the radius of rounding and when the width is greater than the length of the substrate minus radius. In this case it is necessary to determine the angles of rounding’s drawing and the position of chord of circle, all the calculations can be made with the help of geometry course, see the picture below:

Plus we add the shadow on the right angle of the progress to attach the volume effect.
At the end we get the following drawing code of the progress:
[php]function progressBarRect(ctx, x, y, width, height, radius, max) {
// deplacement for chord drawing
var offset = 0;
ctx.beginPath();
if (width<radius) {
offset = radius – Math.sqrt(Math.pow(radius,2)-Math.pow((radius-width),2));
// Left angle
var left_angle = Math.acos((radius – width) / radius);
ctx.moveTo(x + width, y+offset);
ctx.lineTo(x + width, y+height-offset);
ctx.arc(x + radius, y + radius, radius, Math.PI – left_angle, Math.PI + left_angle, false);
}
else if (width+radius>max) {
offset = radius – Math.sqrt(Math.pow(radius,2)-Math.pow((radius – (max-width)),2));
// Right angle
var right_angle = Math.acos((radius – (max-width)) / radius);
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width, y);
ctx.arc(x+max-radius, y + radius, radius, -Math.PI/2, -right_angle, false);
ctx.lineTo(x + width, y+height-offset);
ctx.arc(x+max-radius, y + radius, radius, right_angle, Math.PI/2, false);
ctx.lineTo(x + radius, y + height);
ctx.arc(x+radius, y+radius, radius, Math.PI/2, 3*Math.PI/2, false);
}
else {
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + height);
ctx.lineTo(x + radius, y + height);
ctx.arc(x+radius, y+radius, radius, Math.PI/2, 3*Math.PI/2, false);
}
ctx.closePath();
ctx.fill();

// shadow on the right
if (width<max-1) {
ctx.save();
ctx.shadowOffsetX = 1;
ctx.shadowBlur = 1;
ctx.shadowColor = ‘#666′;
if (width+radius>max)
offset = offset+1;
ctx.fillRect(x+width,y+offset,1,total_height-offset*2);
ctx.restore();
}
}
[/php]

Here, unlike the previous function, we don’t save and renew the context as in case with the substrate, because we are going to define context’s global attributes for the progress.

Text of the progress

Now we add the text that will display the state of progress in per cent. If the length of the text is less than the length of the progress, text will be placed directly at the right angle of the progress, otherwise it will be on the left angle of the progress. To measure the text length we use measureText() function:

[php]function progressText(ctx, x, y, width, height, radius, max) {
ctx.save();
ctx.fillStyle = ‘white’;
var text = Math.floor(width/max*100)+"%";
var text_width = ctx.measureText(text).width;
var text_x = x+width-text_width-radius/2;
if (width<=radius+text_width) {
text_x = x+radius/2;
}
ctx.fillText(text, text_x, y+22);
ctx.restore();
}
[/php]

Full-fledged progress indicator

Now we need to unite all the parts together and define the animation that will fill the progress from 0 to 100%. To create the animation we use sеtInterval() and clearInterval() functions:

[php]// Define the size and position of indicator
var i = 0;
var res = 0;
var context = null;
var total_width = 300;
var total_height = 34;
var initial_x = 20;
var initial_y = 20;
var radius = total_height/2;

window.onload = function() {
// Get the canvas element
var elem = document.getElementById(‘myCanvas’);
// Check the canvas support with the help of browser
if (!elem || !elem.getContext) {
return;
}

context = elem.getContext(’2d’);
if (!context) {
return;
}

// Text’s font of the progress
context.font = "16px Verdana";

// Gradient of the progress
var progress_lingrad = context.createLinearGradient(0,initial_y+total_height,0,0);
progress_lingrad.addColorStop(0, ‘#4DA4F3′);
progress_lingrad.addColorStop(0.4, ‘#ADD9FF’);
progress_lingrad.addColorStop(1, ‘#9ED1FF’);
context.fillStyle = progress_lingrad;

// Create the animation
res = setInterval(draw, 50);
}

function draw() {
// augment the length on 1 for every iteration
i+=1;

// Clear the layer
context.clearRect(initial_x-5,initial_y-5,total_width+15,total_height+15);
progressLayerRect(context, initial_x, initial_y, total_width, total_height, radius);
progressBarRect(context, initial_x, initial_y, i, total_height, radius, total_width);
progressText(context, initial_x, initial_y, i, total_height, radius, total_width );
// stop the animation when it reaches 100%
if (i>=total_width) {
clearInterval(res);
}
}
[/php]

That’s all, now all we need is to put javascript code in script tag, and in body document we should add:
[php]<canvas id="myCanvas" width="500" height="150">Your browser does not have support for canvas.</canvas>[/php]

The link for the final result: http://pastehtml.com/view/1d7z824.html

Inset Shadow

The thing is that there is no such tools for creation of the inset shadow in canvas as easy as in CSS, and the essence of the method described above is in using the «Opposite winding» technique that also can be used in canvas.
And finally, I prepared another style of the substrate that can be created with the help of inset shadow, in this case the substrate will be sunken inside. The original idea was taken here: http://weblog.bocoup.com/can-i-draw-an-inset-shadow-with-the-html5-canvas-api

To reach the desirable effect first you need to draw an inset shadow, you will first need to punch the shape of your hole in a clockwise direction, onto a large square being drawn in an anti-clockwise direction. Once you have done this, it’s simply a matter of adding your shadow parameters to the Canvas context. To get the inset shadow effect we need to create a new function roundInsetRect() and to modify progressLayerRect() function in the following way:

[php]function roundInsetRect(ctx, x, y, width, height, radius) {
ctx.beginPath();

// draw the outward rectangle in an anti-clockwise direction
ctx.moveTo(1000, 1000);
ctx.lineTo(1000, -1000);
ctx.lineTo(-1000, -1000);
ctx.lineTo(-1000, 1000);
ctx.lineTo(1000, 1000);

ctx.moveTo(x + radius, y);
ctx.lineTo(x + width – radius, y);
ctx.arc(x+width-radius, y+radius, radius, -Math.PI/2, Math.PI/2, false);
ctx.lineTo(x + radius, y + height);
ctx.arc(x+radius, y+radius, radius, Math.PI/2, 3*Math.PI/2, false);
ctx.closePath();
ctx.fill();
}
function progressLayerRect(ctx, x, y, width, height, radius) {
ctx.save();
// define the shadows
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 5;
ctx.shadowColor = ‘#666′;

// first gray layer
ctx.fillStyle = ‘rgba(189,189,189,1)’;
roundRect(ctx, x, y, width, height, radius);

ctx.fillStyle = ‘white’;
roundInsetRect(ctx, x, y, width, height, radius);

ctx.restore();
}
[/php]

Here you can see the way it will look: http://pastehtml.com/view/1d80e6t.html

7 Comments

  • Shrikant Kesarkar

    very good tutorial

  • http://feignedinsanity.com/ Rizwan Iqbal

    This is a good tutorial for the canvas element, but am afraid to say thats about it.  I want to know how I can connect this to my body onload event OR use it to check if my entire page is loaded. How do I use this like an actual loader.

  • http://www.micky2be.com micky2be

    Why using canvas when you can use HTML element and CSS3.
    The code would be shorter and more efficient.

    • http://twitter.com/kinduff Alejandro AR

      Only for the style, the animation would be more complicated.

  • http://twitter.com/YaegerDesign Yaeger Design

    Very in depth tutorial. I, also, would like to see the same thing done using CSS3.

  • http://www.rackmountsales.com/Cat5_KVM_Switches_s/56.htm USB KVM

    Thanks for sharing your trend insight this was forwarded along to me and I
    have to say, it’s just a useful and refreshing view on progress bar. I agree
    that HTML5 will affect SEO in the future, so developers and SEOs better start
    today.

     

  • Gustavo

    hi, good tutorial, however if you make body background-color:black (or whatever color), you can see a white rectangle around the load bar, how can make it the white rectangle transparent?

  • Advertisement