Mobile Web Apps

Mobile Web: Create an SVG Loading Spinner


This tutorial will guide you through the development of a simple SVG loading spinner for use on mobile web sites. Visual indicators like the spinner built in this tutorial are used to indicate background-thread activity and are a crucial part of strong user experience design!


This tutorial is assumes that you already have basic knowledge in Scalable Vector Graphics (SVG), HTML, CSS, JavaScript, and jQuery. However, the content is presented in a step-by-step fashion that should be easy enough to follow along.

What about Raphaël? We will use the Raphaël project for performing the SVG drawing in this tutorial. To quote from the official Raphaël project web site:

Raphaël uses the SVG W3C Recommendation and VML as a base for creating graphics. This means every graphical object you create is also a DOM object, so you can attach JavaScript event handlers or modify them later. Raphaël’s goal is to provide an adapter that will make drawing vector art compatible cross-browser and easy.

To use Raphaël in your project, you just need to follow these steps:

  1. Import the library into your webpage.
  2. Create the raphael object, passing the id of the div where your SVG will be drawn, like so:
    var paper = Raphael(divID, width, height);
  3. Create the elements you need into the recently created raphael object, for example:
    // Creates circle at x = 50, y = 40, with radius 10
    var circle =, 40, 10);
    // Sets the fill attribute of the circle to red (#f00)
    circle.attr("fill", "#f00");

Enough theory! Let's start coding!

Step 1: Page Creation with HTML

Let's start by first building our demo page in HTML. It should look like the following:

<!DOCTYPE html>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Loading Spinner Example</title>
         <!-- CSS -->
        <link href="spinner/spinner.css" rel="stylesheet">
            <a id="createSpinner" href="">Unleash</a> the power of the loading spinner.

        <!-- SPINNER -->
        <div id="spinnerFullScreen">
            <div id="floater">
                <div id="spinner"></div>

        <!-- Placed at the end of the document so the pages load faster and without blocking -->
        <script src=""
        <script src=""
        <script src="spinner/spinner.js" type="text/javascript"></script>

Last, but not the least, we add a link where you can click to "unleash" the spinner (i.e. begin the spinning animation).

<a id="createSpinner">Unleash</a> the power of the loading spinner.

Step 2: CSS Styling

Now that we have our markup ready, we need to begin filling in the missing style.

In terms of CSS, the outermost div (i.e. id="spinnerFullScreen") must be black and occupy the whole screen on top of all elements that don't belong to the spinner.

The other two divs (i.e. id="floater" and id="spinner") uses a slight "hack" in order to properly center the spinner in the middle of the screen no matter what the screen size is or where the scroll is set. I will not explain it on this tutorial since the CSS only relates to a "dummy" demo page, not the central purpose of this tutorial.

In the end, the spinner.css file should look like this:

#spinnerFullScreen {
    display: none;
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0px;
    left: 0px;
    background-color: black;
    opacity: 0;
    z-index: 9999998;

#floater {
    display: table;
    width: 100%;
    height: 100%;

#spinner {
    display: table-cell;
    vertical-align: middle;
    text-align: center;

Step 3: Adding Behavior with JavaScript

In theory, our spinner is composed of a certain number of sectors (8 in the image) that have a length ("sectorLength") and a width ("sectorWidth"). Of course, these sectors have a distance to the center as well ("centerRadius").

Spinner math

But is this static? And what about the animation? Well, the animation is just a little trick: having all the sector opacities ranging from 0.0 to 1.0, we continuously change the opacity of each sector to be equal to the opacity of the next sector. Confused? It will likely become more transparent once you see the implementation in JavaScript.

In order to create a reusable library, we will use an Object Oriented paradigm implemented in JavaScript. The library is built around a constructor (function Spinner(data)) and two distinct functions:

  • create – using the instance variables defined in the constructor, it builds the SVG spinner and animates it as well.
  • destroy – destroys the SVG spinner and hides the full screen view.

In the spinner.js file created previously, we first create the constructor of the Spinner, enabling the user of the library to set some values like the number of sectors, the distance of the sectors to the center, and so forth.

 * creates the object Spinner with data values or default values in the case they are missing
 * @param data
 * @constructor

function Spinner(data) {

    //number of sectors of the spinner - default = 12
    this.sectorsCount = data.sectorsCount || 12;

    //the distance from each sector to the center - default = 70
    this.centerRadius = data.centerRadius || 70;

    //the length/height of each sector - default = 120
    this.sectorLength = data.sectorLength || 120;

    //the width of each sector of the spinner - default = 25
    this.sectorWidth = data.sectorWidth || 25;

    //color of the spinner - default = white
    this.color = data.color || 'white';

    //the opacity of the fullScreen
    this.fullScreenOpacity = data.fullScreenOpacity;

    //array of spinner sectors, each spinner is a svg path
    this.sectors = [];

    //array with the opacity of each sector
    this.opacity = [];

    //the raphael spinner object
    this.spinnerObject = null;

    //id of the timeout function for the rotating animation
    this.spinnerTick = null;

Now on to the biggest method of the spinner object, the create method. This method is called every time the user wants to show the spinner. Note the use of jQuery to select our elements. This is where the id's we talked about above come in:

Spinner.prototype.create = function() {
    //shows the full screen spinner div

    //animates the opacity of the full screen div containing the spinner from 0 to 0.8
        opacity: this.fullScreenOpacity
    }, 1000, function() {

Continuing along with the create method, we do some initial calculations, like the total size of the spinner, and prepare the Raphael object to draw the sections:

    //center point of the canvas/spinner/raphael object
    var spinnerCenter = this.centerRadius + this.sectorLength + this.sectorWidth;

    //angle difference/step between each sector
    var beta = 2 * Math.PI / this.sectorsCount;

    //params for each sector/path (stroke-color, stroke-width, stroke-linecap)
    var pathParams = {
        "stroke": this.color,
        "stroke-width": this.sectorWidth,
        "stroke-linecap": "round"

     * creates the Raphael object with a width and a height
     * equals to the double of the spinner center
     * “spinner” is the id of the div where the elements will be drawn
    var paperSize = 2 * spinnerCenter;
    this.spinnerObject = Raphael('spinner', paperSize, paperSize);

Next is the drawing of the cycle and the building of an array with the current opacity of each sector:

    //builds the sectors and the respective opacity
    for (var i = 0; i < this.sectorsCount; i++) {

        //angle of the current sector
        var alpha = beta * i;
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);

        //opacity of the current sector
        this.opacity[i] = 1 / this.sectorsCount * i;

         * builds each sector, which in reality is a SVG path
         * note that Upper case letter means that the command is absolute,
         * lower case means relative to the current position.
         * (
	   * we move the "cursor" to the center of the spinner
	   * and add the centerRadius to center to move to the beginning of each sector
		 * and draws a line with length = sectorLength to the final point
		 * (which takes into account the current drawing angle)
        this.sectors[i] = this.spinnerObject.path([
            ["M", spinnerCenter + this.centerRadius * cos, spinnerCenter + this.centerRadius * sin],
            ["l", this.sectorLength * cos, this.sectorLength * sin]

Now that we have our spinner built and displayed, we need to animate it. This is the last part of the create method:

     * does an animation step and calls itself again
     * @param spinnerObject this param needs to be passed
     * because of scope changes when called through setTimeout function
    (function animationStep(spinnerObject) {

        //shifts to the right the opacity of the sectors

        //updates the opacity of the sectors
        for (var i = 0; i < spinnerObject.sectorsCount; i++) {
            spinnerObject.sectors[i].attr("opacity", spinnerObject.opacity[i]);

         * safari browser helper
         * There is an inconvenient rendering bug in Safari (WebKit):
         * sometimes the rendering should be forced.
         * This method should help with dealing with this bug.
         * source:

         * calls the animation step again
         * it's called in each second, the number of sectors the spinner has.
         * So the spinner gives a round each second, independently the number of sectors it has
         * note: doesn't work on IE passing parameter with the settimeout function :(
        spinnerObject.spinnerTick = setTimeout(animationStep, 1000 / spinnerObject.sectorsCount, spinnerObject);

};//end of the create method

Finally, the destroy method of our spinner:

 * destroys the spinner and hides the full screen div
Spinner.prototype.destroy = function() {
    //stops the animation function

    //removes the Raphael spinner object
    this.spinnerObject = null;

    //animates the opacity of the div to 0 again and hides it (display:none) in the end
        opacity: 0
    }, 2000, function() {

Step 4: Unleash the power!

With the spinning code in place, it's now time to attach an event to the link, so that when the user clicks it, we show the spinner for a 6 second interval. Personally, I use this for asynchronous requests to the server, and when the request is over I simply remove the spinner.

Note that this code can only be used after all the libraries that the spinner depends on are loaded. You can add this code in the end of the spinner.js file or in another JavaScript file if you want to keep the spinner.js file independent and reusable for other projects.

$(document).ready(function() {

function unleashSpinner() {
    var data = {};
    data.centerRadius = 35;
    data.sectorLength = 50;
    data.sectorsCount = 10;
    data.sectorWidth = 20;
    data.color = 'white';
    data.fullScreenOpacity = 0.8;

    var spinner = new Spinner(data);

    setTimeout(function(){spinner.destroy();}, 6000);

    return false;

We can reuse the spinner variable as many times as we want.

Wrap Up

The spinner demonstrated in this tutorial can be used in web pages designed not only for mobile devices, but also for "normal" web pages. I already tried this with both methods, and it worked just fine!

In order to test your knowledge, you can work on improving the current spinner implementation in a few unique ways. For instance, you could try changing the format/shape of the sections, enabling clockwise or anti-clockwise movement, or enable a developer to pick any id for the spinner in order to avoid id clashes.

That's it for this time. I hope you enjoyed this tutorial!

Related Posts
  • Web Design
    Case Studies
    How They Did It: CrowdpilotCrowdpilot thumb
    Crowdpilot's landing page shows off just how simple and awesome SVG is in combination with JavaScript and CSS animations. In this tutorial, you'll learn how to recreate Crowdpilot's diagonal message rotator and curtain, plus we'll talk a bit about "flat" design and what it means to design digitally native elements.Read More…
  • Code
    HTML5: Battery Status APIPdl54 preview image@2x
    The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.Read More…
  • Web Design
    Walk Users Through Your Website With Bootstrap TourTour retina
    When you have a web application which requires some getting used to from your users, a walkthrough of the interface is in order. Creating a walkthrough directly on top of the interface makes things very clear, so that's what we're going to build, using Bootstrap Tour.Read More…
  • Web Design
    How to Animate Festive SVG Icons With CSSAnimated icons retina
    'Tis the season, so in this tutorial, I'll walk through creating some CSS animated, holiday-themed, SVG icons. There are some great icons on Iconmelon, a site which hosts many free vector icon sets for you to sink your teeth into. The icons I'm using come courtesy of designer Sam Jones. So grab yourself a cup of eggnog, pull your laptop up to the yule log, and let's gets started!Read More…
  • Code
    JavaScript & AJAX
    Working With IndexedDB - Part 2Indexeddb retina preview
    Welcome to the second part of my IndexedDB article. I strongly recommend reading the first article in this series, as I'll be assuming you are familiar with all the concepts covered so far. In this article, we're going to wrap up the CRUD aspects we didn't finish before (specifically updating and deleting content), and then demonstrate a real world application that we will use to demonstrate other concepts in the final article.Read More…
  • Web Design
    Making Web Icons SmarterIconic retina
    This article is the first in a three-part series showing the new approaches to iconography Iconic will be delivering. If you like what you see in this article, please consider backing Iconic on Kickstarter.Read More…