xpoopx
xpoopx

Reputation: 41

Creating circular countdown timer with canvas

There is a lot of circle progress bar plugins but none of them is what I need. Basically all the plugins go from 0 degrees to 360 degrees or 0 to 270 degrees or something like that. What I need is circular progress bar that decreases for example from 120 degrees to 0 degrees in 10 seconds. I've been able to do the 120 decrease with the plugins but it is always from 360 degrees to 240 degrees. So I want plugin that starts from let's say 1/3 of circle filled to 0/3 circle filled.

Upvotes: 0

Views: 1375

Answers (2)

Blindman67
Blindman67

Reputation: 54049

How to make a JQuery arcTimer/progress bar

This is also my first JQuery plugin so if anyone sees fault please do point it out.

Making a jquery plugin is straightforward. You need to implement a few methods to get the ball rolling. First is a anonymous function that declares the plugin.

(function ($, window, document, undefined) {
    var pluginName = "arcTimer"; // requiered
    var defaults = {             // requiered
        // you defaults
        // todo see below for setting up defaults
    };

    // this is called to start your plugin.
    // element is the element that the plugin will run in
    // options ar the options passed to the plugin
    function Plugin(element, options) { // "this" is the plugin 
        this.element = element; 
        this.settings = $.extend({}, defaults, options); // gets the setting applying defaults as needed
        this._defaults = defaults; // save the defaults. Not sure if this is needed
        this._name = pluginName;  // save the name
        this._init();   // call your plugin startup code.
    }
    // now define the plugin prototype.
    Plugin.prototype = {
       // todo see below for setting plugin prototype
       _init:function(){ // internal functions and properties are generally prefixed with _
           // todo see below for init code
       }
    }
    // this defines you plugin in the jQuery scope.
    $.fn[pluginName] = function (options) {
        var plugin;
        this.each(function () {
            plugin = $.data(this, "plugin_" + pluginName);
            if (!plugin) {
                plugin = new Plugin(this, options);
                $.data(this, "plugin_" + pluginName, plugin);
            }
        });
        return plugin;
    };

})(jQuery, window, document);

So that is the basic plugin structure. I have called it "arcTimer" and is to be used as follows

The HTML

  <div id="testPlug" style = "z-index:1000"></div>

The script

var aTimer = $("#testPlug").arcTimer({
   // the settings
});

function timerThing(){
    // set the position.
    setTimeout(timerThing,100); // do every 100ms
}
timerThing(); // start it going.

Lets create the settings

defaults = { // angles in normalised Tua. 0 is 0deg, 1 is 360deg (2*PI)
    arcStart : 0, // start pos of arc
    arcEnd : 1,  // end pos of arc
    backgroundColour : "#bbb", // ouside color
    insideRadius : 60,   // inside radius
    outsideRadius : 80,   // outside radius
    centerRadius : undefined, // my bad should be in plugin prototype. But does not matter that its here for now
    outsideDistance : 4,  // pixel clearance of outside arc;
    outlineColour : "#000",  // line colour
    outlineWidth : 2,      // line width
    barColour : "red",  // bar colour
};

Now to the functionality

Some required methods

// starts the timer at is optional. If not there start the thing at 0
start : function (at) {
    // vet at if there else start at 0
    this.position = at !== undefined ? Math.max(0, Math.min(1, at)) : 0;
    this._draw(); // draw it;
},
set : function (pos) {  // sets the pos between 0-1 then draws
    if (pos !== undefined) {
        this.position = Math.max(0, Math.min(1, pos));
        this._draw();
    }
},
complete : function () { // set the pos at 1;
    this.position = 1;
    this._draw();
},

Some internal variables to add to the prototype

_TUA : Math.PI * 2, // two pie
_X : undefined,  // center of arc X
_Y : undefined,
position : 0,   // the bar position 0-1 is full sweep

Let's create the _inti prototype

// creates a canvas of the correct size to fit the setting we give
// inserts the canvas into the DOM and saves referances to the plugin prototype
_init : function () {
    this.settings.width = (this.settings.outsideRadius + this.settings.outsideDistance + this.settings.outlineWidth) * 2;
    this.settings.height = this.settings.width;
    this._X = this.settings.height / 2;
    this._Y = this.settings.height / 2;
    var $canvas = $("<canvas id=\"arcTimer_" + $(this.element).attr("id") + "\" width=\"" +
            this.settings.width + "\" height=\"" +
            this.settings.height + "\">" +
            "</canvas>");
    $(this.element).prepend($canvas[0]);
    this.can = $canvas[0];
    this.ctx = $canvas[0].getContext("2d");
    this.ctx.textAlign = "center";
    this.ctx.textBaseline = "middle";
    this.ctx.lineJoin = "round";
    this.ctx.lineCap = "round";
    this.ctx.clearRect(0, 0, this.settings.width, this.settings.height);
},

Next are some internal helper functions. They are rather simple but just as an illistration

// draws a arc with at x,y, with an inner radius r1, outer radius r2, 
// from to are in normalized Tua 0 is 0deg, 1 is 360Deg
_drawCircle : function (x, y, r1, r2, from, to) {
    ctx = this.ctx;
    from = from * this._TUA;
    to = to * this._TUA;
    //log("s")
    ctx.beginPath();
    ctx.arc(x, y, r1, to, from, true);
    ctx.arc(x, y, r2, from, to, false);
    ctx.closePath();
    ctx.stroke();
    ctx.fill();
},
// converts a unit value to its range
_mathRange : function (fraction, start, end) {
    return (end - start) * fraction + start;
},
// gets the arc distance in radians at radius  for pixelDist
_mathDistToAngle : function (pixelDist, radius) {
    return pixelDist / radius;
},

And finally the bit that does all the work.

    _draw : function () {
        var canvas = this.can;  // get the canvas and context
        var ctx = this.ctx;
        // clear the canvas
        ctx.clearRect(0, 0, this.settings.width, this.settings.height);
        // set up styles for outside arc
        ctx.fillStyle = this.settings.backgroundColour;
        ctx.strokeStyle = this.settings.outlineColour;
        ctx.lineWidth = this.settings.outlineWidth;
        // get the angular side of outside clearance
        var pix2Angle = this._mathDistToAngle(this.settings.outsideDistance, this.settings.centerRadius);
        pix2Angle /= this._TUA; // because draw circle use val normalised to tua (PI*2) need to set it to the correct range
        // draw the outside arc
        this._drawCircle(
            this._X,
            this._Y,
            this.settings.insideRadius - this.settings.outsideDistance,
            this.settings.outsideRadius + this.settings.outsideDistance,
            this.settings.arcStart - pix2Angle,
            this.settings.arcEnd + pix2Angle
        );
        // set the inside arc colour
        ctx.fillStyle = this.settings.barColour;
        // draw the inside arc 
        this._drawCircle(
            this._X,
            this._Y,
            this.settings.insideRadius,
            this.settings.outsideRadius,
            this.settings.arcStart,
            this._mathRange(this.position, this.settings.arcStart, this.settings.arcEnd)
        );
    }

That's it, easy realy. Below is it in use.

(function ($, window, document, undefined) {
    var pluginName = "arcTimer",
    defaults = { // angles in normalised Tua. 0 is 0deg, 1 is 360deg (2*PI)
        arcStart : 0, //
        arcEnd : 1,
        backgroundColour : "#bbb",
        insideRadius : 60,
        outsideRadius : 80,
        centerRadius : undefined,
        outsideDistance : 4,
        outlineColour : "#000",
        outlineWidth : 2,
        barColour : "red",

    };

    function Plugin(element, options) {
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this.settings.centerRadius = (this.settings.insideRadius + this.settings.outsideRadius) / 2;
        this._defaults = defaults;
        this._name = pluginName;
        this._init();
    }

    Plugin.prototype = {
        _TUA : Math.PI * 2,
        _X : undefined,
        _Y : undefined,
        position : 0,
        start : function (at) {
            this.position = at !== undefined ? Math.max(0, Math.min(1, at)) : 0;
            this._draw();
        },
        set : function (pos) {
            if (pos !== undefined) {
                this.position = Math.max(0, Math.min(1, pos));
                this._draw();
            }
        },
        complete : function () {
            this.position = 1;
            this._draw();
        },
        _init : function () {
            this.settings.width = (this.settings.outsideRadius + this.settings.outsideDistance + this.settings.outlineWidth) * 2;
            this.settings.height = this.settings.width;
            this._X = this.settings.height / 2;
            this._Y = this.settings.height / 2;
            var $canvas = $("<canvas id=\"arcTimer_" + $(this.element).attr("id") + "\" width=\"" +
                    this.settings.width + "\" height=\"" +
                    this.settings.height + "\">" +
                    "</canvas>");
            $(this.element).prepend($canvas[0]);
            this.can = $canvas[0];
            this.ctx = $canvas[0].getContext("2d");
            this.ctx.textAlign = "center";
            this.ctx.textBaseline = "middle";
            this.ctx.lineJoin = "round";
            this.ctx.lineCap = "round";
            this.ctx.clearRect(0, 0, this.settings.width, this.settings.height);
        },
        _drawCircle : function (x, y, r1, r2, from, to) {
            ctx = this.ctx;
            from = from * this._TUA;
            to = to * this._TUA;
            //log("s")
            ctx.beginPath();
            ctx.arc(x, y, r1, to, from, true);
            ctx.arc(x, y, r2, from, to, false);
            ctx.closePath();
            ctx.stroke();
            ctx.fill();
        },
        _mathRange : function (fraction, start, end) {
            return (end - start) * fraction + start;
        },
        _mathDistToAngle : function (pixelDist, radius) {
            return pixelDist / radius;
        },
        _draw : function () {
            var canvas = this.can;
            var ctx = this.ctx;
            ctx.clearRect(0, 0, this.settings.width, this.settings.height);
            ctx.fillStyle = this.settings.backgroundColour;
            ctx.strokeStyle = this.settings.outlineColour;
            ctx.lineWidth = this.settings.outlineWidth;
            var pix2Angle = this._mathDistToAngle(this.settings.outsideDistance, this.settings.centerRadius);
            pix2Angle /= this._TUA; // because draw circle use val normalised to tua (PI*2) need to set it to the correct range
            this._drawCircle(
                this._X,
                this._Y,
                this.settings.insideRadius - this.settings.outsideDistance,
                this.settings.outsideRadius + this.settings.outsideDistance,
                this.settings.arcStart - pix2Angle,
                this.settings.arcEnd + pix2Angle)
            ctx.fillStyle = this.settings.barColour;
            this._drawCircle(
                this._X,
                this._Y,
                this.settings.insideRadius,
                this.settings.outsideRadius,
                this.settings.arcStart,
                this._mathRange(this.position, this.settings.arcStart, this.settings.arcEnd)
            );
        }
    };

    $.fn[pluginName] = function (options) {
        var plugin;
        this.each(function () {
            plugin = $.data(this, "plugin_" + pluginName);
            if (!plugin) {
                plugin = new Plugin(this, options);
                $.data(this, "plugin_" + pluginName, plugin);
            }
        });
        return plugin;
    };

})(jQuery, window, document);


// create an arc timer from 1/3 to 2/3 of a circle    
var arcT = $("#testPlug").arcTimer({
  arcStart     : 1/3,             // 
  arcEnd       : 2/3,      
  backgroundColour : "#bbb",        
  insideRadius : 60,      
  outsideRadius: 80,
  centerRadius:undefined,
  outsideDistance  : 4,
  outlineColour: "#000",          
  outlineWidth: 2,     
  barColour: "red",      

});
arcT.start();
function timerThing(){
  arcT.position += 0.01;  // update the poistion
  if(arcT.position >= 1){
    arcT.settings.barColour = "#0F0";
    arcT.complete(); // all done.
    // do it again in 2 seconds;
    setTimeout(timerThing,2000);  // up date in 100ms
    arcT.settings.barColour = "#F00";
    arcT.position = 0;
  }else{
    arcT.set(arcT.position); // kind of silly I know but I did not think it out
                          // but I am sure you can modify the plugin to do as needed.
    setTimeout(timerThing,100);  // up date in 100ms
  }
}
timerThing();
    
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="testPlug"></div>

Upvotes: 1

XAF
XAF

Reputation: 1472

Are you looking for something like this ?

https://github.com/johnschult/jquery.countdown360

You can edit it how you want and it will count down from that second. There is a demo already.

Upvotes: 1

Related Questions