Create a whirling timer with HTML5 canvas and Prototype.js
Frontend developer David Smith demonstrates how to use HTML5 canvas in conjunction with the Prototype.js framework to enhance slideshows for your visitors with a simple but accurate loading wheel
In a world where JavaScript frameworks make cross browser DOM manipulation a breeze, it’s hardly surprising that JavaScript based slideshows have now largely replaced their Flash-driven counterparts as the tool of choice for web designers looking to showcase images on their websites.
Nonetheless, there are still only a few scripts which can really claim to provide full feature sets to match the best Flash driven shows in terms of user experience for visitors.
In this quick tutorial, I’ll aim to demonstrate how we can harness the power of the Prototype.js framework and HTML5 canvas to enhance a simple slide show with a whirling timer to provide better feedback for visitors viewing a slideshow. We’ll discover how to utilise the canvas arc method to draw segments and also learn how to use Epoch time to ensure accurate animation timings.
While this tutorial makes use of Prototype.js, there’s really nothing here that you couldn’t emulate in jQuery (or indeed any other JavaScript library). In my experience, it’s valuable to be familiar with other libraries so please follow along and – if necessary – adapt the code later for your favourite JS framework.
Lets get started!
Set up a quick development environment
First things first, we’ll need to setup our development environment. For this simple demo, I’ve started with three key files:
- index.html – a stripped back version of the HTML from the excellent HTML5 Boilerplate project.
- styles.css – the core stylesheets for our demo. I’ve also hooked up a reset.css file to ensure we start with an even playing field.
- show.js – the main JavaScript file. This is where we’ll be writing the majority of our JavaScript for this tutorial (don’t forget this one!).
Once you’ve linked these files together, be sure to include the latest Google CDN version of Prototype.js from the index.html file:
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
Creating simple slideshow
That sorted, let’s jump in and create a basic slideshow. For the purposes of this tutorial we don’t need many features so we’re going to keep things nice and simple. Luckily Johnathon Snook had a similar requirement three years ago and so I’ve used the Simplest jQuery Slideshow as a template for our Prototype.js version.
Fire up show.js in your text editor and add the following code:
var canvasShow = (function(window) { // slideshow code here}(window));
We’re starting out with best practices using a self invoking function to shield our core methods and variables from the global scope. This helps ensure our script doesn’t conflict with other code that might already be running on our page (tip: for more information on this pattern and more I highly recommend Addy Osmani’s Essential Javascript Design Patterns).
With this in place we can start writing our basic show code:
var canvasShow = (function(window){ /** * PRIVATE VARS */ var show, slide_list; /** * INITIALISE * Sets up required vars and sets configuration options * Runs the show */ function init(id) { // Get a reference to the show element show = $(id); slide_list = show.down('ul'); /** * Select all images which are not the first image and hide each one * I use the little known invoke() method - a great asset to Prototype.js * http://api.prototypejs.org/language/Enumerable/prototype/invoke/ */ slide_list.select('li:not(:first)').invoke('hide'); // Run the show runShow(); } /** * THE LOOP * The main show loop - shows and hides slides and reorders the DOM */ function runShow() { var showLoop = setInterval(function(){ // 1) Reselect & cache the new first slide var first = slide_list.select('li').first(); // 2) Hide the first slide and then show the next slide first.hide().next().show(); // 3) Append the first slide to the end of the list slide_list.insert(first); },3000); } /** * REVEAL * Return "public" methods */ return { init: init };}(window));Event.observe(window, 'load', function() { // Call our slideshow canvasShow.init('slideshow');});
We now have two methods in our show. The init() method accepts an the ID of the show element, hides all but the first slide and then fires the runShow() method which controls our show loop. The runShow() method simply loops every three seconds hiding the current first slide, showing the next slide and then shunting the original first slide to the end of the <ul> element.
At the bottom of our script we return init() exposing it as a public method which we then call when our page (including images) is fully loaded.
The eagle eyed amongst you might have noticed that I’m not using setInterval() to control our loop. Instead I’m making use of Prototype.js’ PeriodicalExecuter facility which has the advantage of protecting us against multiple parallel executions of our show loop callback function. It’s a nice little extra baked right into Prototype.js.
Testing our code in the browser reveals our simple slide show is working as expected. It won’t win any awards but it’s good enough for our purposes.
Setting up our canvas
Now it’s time to create our canvas element and prepare it to draw the progress timer. To do this we’ve added a new method setupTimer() which tests for canvas support, and if found, dynamically creates the canvas element and grabs it’s 2D context:
function setupTimer() { canvas = document.createElement('canvas'); if (canvas.getContext && canvas.getContext('2d')) { // check for canvas support canvas.width = 30; canvas.height = 30; ctx = canvas.getContext('2d'); ctx.strokeStyle = 'rgba(255,255,255,.8)';ctx.lineCap = 'butt'; ctx.lineWidth = 4; canvas.writeAttribute('class','timer'); show.insert(canvas); } else { // if canvas support isn't available canvas = false; }}
We’ve also set some basic styles for our timer graphic using the appropriate Canvas API methods. There are plenty of good tutorials which document the basics of drawing with the Canvas API, so I’m not going to bore you by going over old ground here.
With this basic setup complete we’re now ready to actually draw our timer graphic onto our canvas.
Drawing a simple arc with the canvas API
The whirling timer we’re looking to create will be a classic 360deg doughnut shape, similar in form to some AJAX “loading GIFs” commonly in use around the web.
In order to indicate the progress of time, we’ll have our script dynamically draw the segments (degrees) of the doughnut shape starting at 0 and continuing round to the full 360deg. This full circle will chart the passage of time between the current slide appearing at the beginning of the show loop (the show loop is part of the runShow() method from step 2) and it being hidden in favour of the next slide at the end of the show loop.
The first thing we need to do is create a function which will draw an arc on our canvas. To do this we’re going to make use of the the arc method of the Canvas API. If you’re unfamiliar with this method, I highly recommend you read the “Circle” section of Rob Hawkes’ excellent netmag article on Learning the basics of HTML5 canvas which has a great explanation and overview.
Our function for drawing arc’s is as follows:
function drawArc(startAngle,endAngle) { var drawingArc = true; // set boolean value to show we’re drawing // Define our path using the APIctx.beginPath(); ctx.arc(15,15,10, Math.PI/180)*(startAngle-90),(Math.PI/180)*(endAngle-90), false); ctx.stroke(); // draw on the canvas drawingArc = false; }
Our function takes two arguments which define the start and end angle of the arc (in degrees). These are in turn passed as the 4th and 5th arguments to the arc method of the Canvas API. As the arc method expects the start and end angles to be provided in radians we’re manipulating the inputs in two ways before they’re passed as arguments:
- We use a simple expression to convert our degrees into radians on the fly (radians = degrees * PI/180). This allows us to provide inputs in degrees which I think is easier in this context of what we’re trying to achieve.
- As the 0 angle defaults to 3 o’clock (if we imagine a circle as a clock face), I’m subtracting 90 from both the start and end angle in order to have 0 degrees start at 12 o’clock, 90 degrees at 3 o'clock, 180 degrees at 6 o'clock and so on...
We can test our function by adding the following in our init() function:
drawArc(0,90); // draws an arc from 0 degrees to 90 degrees
Creating the timed animation
We’re now able to draw any segment (arc) of our timer using our drawArc() function. To animate our timer through 360 degrees we simply need a way to call the drawArc() function repeatedly, incrementing the second argument (endAngle) until it reaches 360.
The only caveat to this is that we must ensure that our animation occurs over precisely 3,000 milli-seconds as – if you look at our show loop – this is the interval between a slide appearing and the next slide replacing it.
So how do we animate? Your first thought might be to do this using a for loop, but that doesn’t allow us to control the progress of the animation so we can discount that method. The next solution might be to use JavaScript’s setInterval, but that isn’t an accurate method of measuring time because it’s dependant on the CPU load of the users’ computer. Thankfully we have a better solution – Epoch time.
Using Epoch time we can calculate a decimal 'position' variable based on an imaginary animation line running from zero (the start of the animation) to one (the end of the animation). By multiplying this decimal representation of our current position by our final destination (ie: 360), we can determine what value we should pass as endAngle to our drawArc() method.
Using Epoch time is relatively simple and is implemented in our runTimer() method below.
function runTimer() { clearCanvas(); // ensure we've cleared the canvas to avoid over drawing // use Epoch time to ensure code executes in time specified var start = (new Date).getTime(), // returns Epoch time eg: "1327253227656" duration = 3000 * 0.75, // the interval between each slide transition finish = start + duration; var tInterval = setInterval(function(){ clearCanvas(); // ensure we've cleared the canvas to avoid over drawing canvas.removeClassName('canvas-hidden'); var currTime = (new Date).getTime(); // get the current "time" as Epoch // Compare time epoch against finish epoch // returns the position as a decimal (eg: 0.35678) // pos is between 0 (beginning) and 1 (end) var pos = currTime>finish ? 1 : (currTime-start)/duration; var seg = Math.floor(356 * pos); // provides end Angle in degrees // Draw our arc segment drawArc(-4,seg); if (pos >=1) { // if we've reached our goal then stop the loop! clearInterval(tInterval); resetTimer(); } },50) }
We’ll break down the key steps as follows.
After initially clearing the canvas, we set some key variables. Firstly we get an epoch representation of the current time using the getTime() method of JavaScript’s native Date object and assign it to the start variable. This represents the 0 [zero] point on our animation line.
var start = (new Date).getTime(); // returns Epoch
Next we set the duration of our animation to match the duration of our show loop. This ensures the duration of the timer animation matches the interval between slide transitions.
var duration = 3000;
Lastly we calculate the finish variable by adding the duration to the start time. This represents end the of our animation (one on our animation line).
var finish = start + duration;
Next we create an internal loop using setInterval and ask it to repeat very quickly (every 50 milliseconds).
For each loop we request the current time (currTime) using getTime(). If the current time is greater than the finish time we calculated previously then we’ve reached the end of our animation. Otherwise we subtract the start time from our current time and divide that by our known duration to produce a decimal representation of our position on the animation line.
var currTime = (new Date).getTime(); var pos = currTime>finish ? 1 : (currTime-start)/duration;
For the final step in our loop we multiple 360 (the total circumference of our timer) by our position on the animation line to produce a value which we then pass as the second argument to drawArc(). This then draws the required segment of our timer graphic and the whole process repeats until the full 360deg timer circle is drawn.
An example “Epoch” calculation
A simple example may help here. If we say our start time was 8034 and for this iteration of our loop the currTime variable is set to 8745 then:
(8745-8034)/3000
= 0.237
360 * 0.237 = 85 // our final value 85 is what is passed to drawArc()
Thankfully for me (and you) I didn’t have to come up with this maths for calculating accurate animations. Much cleverer people have already done this for us. I highly recommend reading this presentation by Prototype.js core team member Thomas Fuchs, which explains all about Epoch time based animation in much greater detail.
Final implementation and homework
We’ve now produced a working canvas based timer using Prototype.js and the HTML5 Canvas API. The final working code can be seen by downloading the source files for this tutorial.
As you can see our show loop now calls runTimer() at the end of each iteration. This in turn animates our canvas timer from 0 to 360deg over precisely three seconds before our show loop is called again and the whole process repeats.
You can see the final code in action here [link to demo]. I’d also recommend downloading the source code for this article and studying the final implementation. I’ve tried to keep things simple and so it’s by no means perfect, but it should give you a good base to work from should you wish to enhance your own scripts.
For those of you looking for way’s to improve the final code I might suggest the following lines of enquiry:
- Add a 'track' area for the timer by drawing a semi-transparent, full 360deg timer on a second canvas layered behind the first. A nice visual enhancement.
- Refactor and optimise the code to ensure both the timer and the show loop run from a single 'duration' variable. This makes the final code more manageable should you wish to alter the interval between each slide.
- Provide a fallback for browsers that don’t support canvas – design agency Zurb have used a CSS based timer solution for their Orbit slideshow.
- Write a method to fade out the timer at the end of each show loop and have it fade back in again at the beginning.
If you’d like to see some of these features in action then you might like to check out Protoshow – a simple (yet powerful) slide show I wrote using Prototype.js and its sister animation library script.aculo.us.
Protoshow.js is the successor to Deep Blue Sky’s Simple Slide Show script and is under regular development. I’d love to find people to contribute to the project and if you’d like to be involved please join me on GitHub or drop me a line on Twitter.
Good luck enhancing your scripts and please do send me your example implementations in the wild!
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.