Blame | Letzte Änderung | Log anzeigen | RSS feed
/*** jquery-circle-progress - jQuery Plugin to draw animated circular progress bars:* {@link http://kottenator.github.io/jquery-circle-progress/}** @author Rostyslav Bryzgunov <kottenator@gmail.com>* @version 1.2.2* @licence MIT* @preserve*/// UMD factory - https://github.com/umdjs/umd/blob/d31bb6ee7098715e019f52bdfe27b3e4bfd2b97e/templates/jqueryPlugin.js// Uses AMD, CommonJS or browser globals to create a jQuery plugin.(function(factory) {if (typeof define === 'function' && define.amd) {// AMD - register as an anonymous moduledefine(['jquery'], factory);} else if (typeof module === 'object' && module.exports) {// Node/CommonJSvar $ = require('jquery');factory($);module.exports = $;} else {// Browser globalsfactory(jQuery);}})(function($) {/*** Inner implementation of the circle progress bar.* The class is not exposed _yet_ but you can create an instance through jQuery method call.** @param {object} config - You can customize any class member (property or method).* @class* @alias CircleProgress*/function CircleProgress(config) {this.init(config);}CircleProgress.prototype = {//--------------------------------------- public options ---------------------------------------/*** This is the only required option. It should be from `0.0` to `1.0`.* @type {number}* @default 0.0*/value: 0.0,/*** Size of the canvas in pixels.* It's a square so we need only one dimension.* @type {number}* @default 100.0*/size: 100.0,/*** Initial angle for `0.0` value in radians.* @type {number}* @default -Math.PI*/startAngle: -Math.PI,/*** Width of the arc in pixels.* If it's `'auto'` - the value is calculated as `[this.size]{@link CircleProgress#size} / 14`.* @type {number|string}* @default 'auto'*/thickness: 'auto',/*** Fill of the arc. You may set it to:** - solid color:* - `'#3aeabb'`* - `{ color: '#3aeabb' }`* - `{ color: 'rgba(255, 255, 255, .3)' }`* - linear gradient _(left to right)_:* - `{ gradient: ['#3aeabb', '#fdd250'], gradientAngle: Math.PI / 4 }`* - `{ gradient: ['red', 'green', 'blue'], gradientDirection: [x0, y0, x1, y1] }`* - `{ gradient: [["red", .2], ["green", .3], ["blue", .8]] }`* - image:* - `{ image: 'http://i.imgur.com/pT0i89v.png' }`* - `{ image: imageObject }`* - `{ color: 'lime', image: 'http://i.imgur.com/pT0i89v.png' }` -* color displayed until the image is loaded** @default {gradient: ['#3aeabb', '#fdd250']}*/fill: {gradient: ['#3aeabb', '#fdd250']},/*** Color of the "empty" arc. Only a color fill supported by now.* @type {string}* @default 'rgba(0, 0, 0, .1)'*/emptyFill: 'rgba(0, 0, 0, .1)',/*** jQuery Animation config.* You can pass `false` to disable the animation.* @see http://api.jquery.com/animate/* @type {object|boolean}* @default {duration: 1200, easing: 'circleProgressEasing'}*/animation: {duration: 1200,easing: 'circleProgressEasing'},/*** Default animation starts at `0.0` and ends at specified `value`. Let's call this _direct animation_.* If you want to make _reversed animation_ - set `animationStartValue: 1.0`.* Also you may specify any other value from `0.0` to `1.0`.* @type {number}* @default 0.0*/animationStartValue: 0.0,/*** Reverse animation and arc draw.* By default, the arc is filled from `0.0` to `value`, _clockwise_.* With `reverse: true` the arc is filled from `1.0` to `value`, _counter-clockwise_.* @type {boolean}* @default false*/reverse: false,/*** Arc line cap: `'butt'`, `'round'` or `'square'` -* [read more]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap}.* @type {string}* @default 'butt'*/lineCap: 'butt',/*** Canvas insertion mode: append or prepend it into the parent element?* @type {string}* @default 'prepend'*/insertMode: 'prepend',//------------------------------ protected properties and methods ------------------------------/*** Link to {@link CircleProgress} constructor.* @protected*/constructor: CircleProgress,/*** Container element. Should be passed into constructor config.* @protected* @type {jQuery}*/el: null,/*** Canvas element. Automatically generated and prepended to [this.el]{@link CircleProgress#el}.* @protected* @type {HTMLCanvasElement}*/canvas: null,/*** 2D-context of [this.canvas]{@link CircleProgress#canvas}.* @protected* @type {CanvasRenderingContext2D}*/ctx: null,/*** Radius of the outer circle. Automatically calculated as `[this.size]{@link CircleProgress#size} / 2`.* @protected* @type {number}*/radius: 0.0,/*** Fill of the main arc. Automatically calculated, depending on [this.fill]{@link CircleProgress#fill} option.* @protected* @type {string|CanvasGradient|CanvasPattern}*/arcFill: null,/*** Last rendered frame value.* @protected* @type {number}*/lastFrameValue: 0.0,/*** Init/re-init the widget.** Throws a jQuery event:** - `circle-inited(jqEvent)`** @param {object} config - You can customize any class member (property or method).*/init: function(config) {$.extend(this, config);this.radius = this.size / 2;this.initWidget();this.initFill();this.draw();this.el.trigger('circle-inited');},/*** Initialize `<canvas>`.* @protected*/initWidget: function() {if (!this.canvas)this.canvas = $('<canvas>')[this.insertMode == 'prepend' ? 'prependTo' : 'appendTo'](this.el)[0];var canvas = this.canvas;canvas.width = this.size;canvas.height = this.size;this.ctx = canvas.getContext('2d');if (window.devicePixelRatio > 1) {var scaleBy = window.devicePixelRatio;canvas.style.width = canvas.style.height = this.size + 'px';canvas.width = canvas.height = this.size * scaleBy;this.ctx.scale(scaleBy, scaleBy);}},/*** This method sets [this.arcFill]{@link CircleProgress#arcFill}.* It could do this async (on image load).* @protected*/initFill: function() {var self = this,fill = this.fill,ctx = this.ctx,size = this.size;if (!fill)throw Error("The fill is not specified!");if (typeof fill == 'string')fill = {color: fill};if (fill.color)this.arcFill = fill.color;if (fill.gradient) {var gr = fill.gradient;if (gr.length == 1) {this.arcFill = gr[0];} else if (gr.length > 1) {var ga = fill.gradientAngle || 0, // gradient direction angle; 0 by defaultgd = fill.gradientDirection || [size / 2 * (1 - Math.cos(ga)), // x0size / 2 * (1 + Math.sin(ga)), // y0size / 2 * (1 + Math.cos(ga)), // x1size / 2 * (1 - Math.sin(ga)) // y1];var lg = ctx.createLinearGradient.apply(ctx, gd);for (var i = 0; i < gr.length; i++) {var color = gr[i],pos = i / (gr.length - 1);if ($.isArray(color)) {pos = color[1];color = color[0];}lg.addColorStop(pos, color);}this.arcFill = lg;}}if (fill.image) {var img;if (fill.image instanceof Image) {img = fill.image;} else {img = new Image();img.src = fill.image;}if (img.complete)setImageFill();elseimg.onload = setImageFill;}function setImageFill() {var bg = $('<canvas>')[0];bg.width = self.size;bg.height = self.size;bg.getContext('2d').drawImage(img, 0, 0, size, size);self.arcFill = self.ctx.createPattern(bg, 'no-repeat');self.drawFrame(self.lastFrameValue);}},/*** Draw the circle.* @protected*/draw: function() {if (this.animation)this.drawAnimated(this.value);elsethis.drawFrame(this.value);},/*** Draw a single animation frame.* @protected* @param {number} v - Frame value.*/drawFrame: function(v) {this.lastFrameValue = v;this.ctx.clearRect(0, 0, this.size, this.size);this.drawEmptyArc(v);this.drawArc(v);},/*** Draw the arc (part of the circle).* @protected* @param {number} v - Frame value.*/drawArc: function(v) {if (v === 0)return;var ctx = this.ctx,r = this.radius,t = this.getThickness(),a = this.startAngle;ctx.save();ctx.beginPath();if (!this.reverse) {ctx.arc(r, r, r - t / 2, a, a + Math.PI * 2 * v);} else {ctx.arc(r, r, r - t / 2, a - Math.PI * 2 * v, a);}ctx.lineWidth = t;ctx.lineCap = this.lineCap;ctx.strokeStyle = this.arcFill;ctx.stroke();ctx.restore();},/*** Draw the _empty (background)_ arc (part of the circle).* @protected* @param {number} v - Frame value.*/drawEmptyArc: function(v) {var ctx = this.ctx,r = this.radius,t = this.getThickness(),a = this.startAngle;if (v < 1) {ctx.save();ctx.beginPath();if (v <= 0) {ctx.arc(r, r, r - t / 2, 0, Math.PI * 2);} else {if (!this.reverse) {ctx.arc(r, r, r - t / 2, a + Math.PI * 2 * v, a);} else {ctx.arc(r, r, r - t / 2, a, a - Math.PI * 2 * v);}}ctx.lineWidth = t;ctx.strokeStyle = this.emptyFill;ctx.stroke();ctx.restore();}},/*** Animate the progress bar.** Throws 3 jQuery events:** - `circle-animation-start(jqEvent)`* - `circle-animation-progress(jqEvent, animationProgress, stepValue)` - multiple event* animationProgress: from `0.0` to `1.0`; stepValue: from `0.0` to `value`* - `circle-animation-end(jqEvent)`** @protected* @param {number} v - Final value.*/drawAnimated: function(v) {var self = this,el = this.el,canvas = $(this.canvas);// stop previous animation before new "start" event is triggeredcanvas.stop(true, false);el.trigger('circle-animation-start');canvas.css({animationProgress: 0}).animate({animationProgress: 1}, $.extend({}, this.animation, {step: function(animationProgress) {var stepValue = self.animationStartValue * (1 - animationProgress) + v * animationProgress;self.drawFrame(stepValue);el.trigger('circle-animation-progress', [animationProgress, stepValue]);}})).promise().always(function() {// trigger on both successful & failure animation endel.trigger('circle-animation-end');});},/*** Get the circle thickness.* @see CircleProgress#thickness* @protected* @returns {number}*/getThickness: function() {return $.isNumeric(this.thickness) ? this.thickness : this.size / 14;},/*** Get current value.* @protected* @return {number}*/getValue: function() {return this.value;},/*** Set current value (with smooth animation transition).* @protected* @param {number} newValue*/setValue: function(newValue) {if (this.animation)this.animationStartValue = this.lastFrameValue;this.value = newValue;this.draw();}};//----------------------------------- Initiating jQuery plugin -----------------------------------$.circleProgress = {// Default options (you may override them)defaults: CircleProgress.prototype};// ease-in-out-cubic$.easing.circleProgressEasing = function(x) {if (x < 0.5) {x = 2 * x;return 0.5 * x * x * x;} else {x = 2 - 2 * x;return 1 - 0.5 * x * x * x;}};/*** Creates an instance of {@link CircleProgress}.* Produces [init event]{@link CircleProgress#init} and [animation events]{@link CircleProgress#drawAnimated}.** @param {object} [configOrCommand] - Config object or command name.** Config example (you can specify any {@link CircleProgress} property):** ```js* { value: 0.75, size: 50, animation: false }* ```** Commands:** ```js* el.circleProgress('widget'); // get the <canvas>* el.circleProgress('value'); // get the value* el.circleProgress('value', newValue); // update the value* el.circleProgress('redraw'); // redraw the circle* el.circleProgress(); // the same as 'redraw'* ```** @param {string} [commandArgument] - Some commands (like `'value'`) may require an argument.* @see CircleProgress* @alias "$(...).circleProgress"*/$.fn.circleProgress = function(configOrCommand, commandArgument) {var dataName = 'circle-progress',firstInstance = this.data(dataName);if (configOrCommand == 'widget') {if (!firstInstance)throw Error('Calling "widget" method on not initialized instance is forbidden');return firstInstance.canvas;}if (configOrCommand == 'value') {if (!firstInstance)throw Error('Calling "value" method on not initialized instance is forbidden');if (typeof commandArgument == 'undefined') {return firstInstance.getValue();} else {var newValue = arguments[1];return this.each(function() {$(this).data(dataName).setValue(newValue);});}}return this.each(function() {var el = $(this),instance = el.data(dataName),config = $.isPlainObject(configOrCommand) ? configOrCommand : {};if (instance) {instance.init(config);} else {var initialConfig = $.extend({}, el.data());if (typeof initialConfig.fill == 'string')initialConfig.fill = JSON.parse(initialConfig.fill);if (typeof initialConfig.animation == 'string')initialConfig.animation = JSON.parse(initialConfig.animation);config = $.extend(initialConfig, config);config.el = el;instance = new CircleProgress(config);el.data(dataName, instance);}});};});