circle-progress.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. jquery-circle-progress - jQuery Plugin to draw animated circular progress bars
  3. URL: http://kottenator.github.io/jquery-circle-progress/
  4. Author: Rostyslav Bryzgunov <kottenator@gmail.com>
  5. Version: 1.1.2
  6. License: MIT
  7. */
  8. (function($) {
  9. function CircleProgress(config) {
  10. this.init(config);
  11. }
  12. CircleProgress.prototype = {
  13. //----------------------------------------------- public options -----------------------------------------------
  14. /**
  15. * This is the only required option. It should be from 0.0 to 1.0
  16. * @type {number}
  17. */
  18. value: 0.0,
  19. /**
  20. * Size of the circle / canvas in pixels
  21. * @type {number}
  22. */
  23. size: 100.0,
  24. /**
  25. * Initial angle for 0.0 value in radians
  26. * @type {number}
  27. */
  28. startAngle: -Math.PI,
  29. /**
  30. * Width of the arc. By default it's auto-calculated as 1/14 of size, but you may set it explicitly in pixels
  31. * @type {number|string}
  32. */
  33. thickness: 'auto',
  34. /**
  35. * Fill of the arc. You may set it to:
  36. * - solid color:
  37. * - { color: '#3aeabb' }
  38. * - { color: 'rgba(255, 255, 255, .3)' }
  39. * - linear gradient (left to right):
  40. * - { gradient: ['#3aeabb', '#fdd250'], gradientAngle: Math.PI / 4 }
  41. * - { gradient: ['red', 'green', 'blue'], gradientDirection: [x0, y0, x1, y1] }
  42. * - image:
  43. * - { image: 'http://i.imgur.com/pT0i89v.png' }
  44. * - { image: imageObject }
  45. * - { color: 'lime', image: 'http://i.imgur.com/pT0i89v.png' } - color displayed until the image is loaded
  46. */
  47. fill: {
  48. gradient: ['#3aeabb', '#fdd250']
  49. },
  50. /**
  51. * Color of the "empty" arc. Only a color fill supported by now
  52. * @type {string}
  53. */
  54. emptyFill: 'rgba(0, 0, 0, .1)',
  55. /**
  56. * Animation config (see jQuery animations: http://api.jquery.com/animate/)
  57. */
  58. animation: {
  59. duration: 1200,
  60. easing: 'circleProgressEasing'
  61. },
  62. /**
  63. * Default animation starts at 0.0 and ends at specified `value`. Let's call this direct animation.
  64. * If you want to make reversed animation then you should set `animationStartValue` to 1.0.
  65. * Also you may specify any other value from 0.0 to 1.0
  66. * @type {number}
  67. */
  68. animationStartValue: 0.0,
  69. /**
  70. * Reverse animation and arc draw
  71. * @type {boolean}
  72. */
  73. reverse: false,
  74. /**
  75. * Arc line cap ('butt', 'round' or 'square')
  76. * Read more: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap
  77. * @type {string}
  78. */
  79. lineCap: 'butt',
  80. //-------------------------------------- protected properties and methods --------------------------------------
  81. /**
  82. * @protected
  83. */
  84. constructor: CircleProgress,
  85. /**
  86. * Container element. Should be passed into constructor config
  87. * @protected
  88. * @type {jQuery}
  89. */
  90. el: null,
  91. /**
  92. * Canvas element. Automatically generated and prepended to the {@link CircleProgress.el container}
  93. * @protected
  94. * @type {HTMLCanvasElement}
  95. */
  96. canvas: null,
  97. /**
  98. * 2D-context of the {@link CircleProgress.canvas canvas}
  99. * @protected
  100. * @type {CanvasRenderingContext2D}
  101. */
  102. ctx: null,
  103. /**
  104. * Radius of the outer circle. Automatically calculated as {@link CircleProgress.size} / 2
  105. * @protected
  106. * @type {number}
  107. */
  108. radius: 0.0,
  109. /**
  110. * Fill of the main arc. Automatically calculated, depending on {@link CircleProgress.fill} option
  111. * @protected
  112. * @type {string|CanvasGradient|CanvasPattern}
  113. */
  114. arcFill: null,
  115. /**
  116. * Last rendered frame value
  117. * @protected
  118. * @type {number}
  119. */
  120. lastFrameValue: 0.0,
  121. /**
  122. * Init/re-init the widget
  123. * @param {object} config Config
  124. */
  125. init: function(config) {
  126. $.extend(this, config);
  127. this.radius = this.size / 2;
  128. this.initWidget();
  129. this.initFill();
  130. this.draw();
  131. },
  132. /**
  133. * @protected
  134. */
  135. initWidget: function() {
  136. var canvas = this.canvas = this.canvas || $('<canvas>').prependTo(this.el)[0];
  137. canvas.width = this.size;
  138. canvas.height = this.size;
  139. this.ctx = canvas.getContext('2d');
  140. },
  141. /**
  142. * This method sets {@link CircleProgress.arcFill}
  143. * It could do this async (on image load)
  144. * @protected
  145. */
  146. initFill: function() {
  147. var self = this,
  148. fill = this.fill,
  149. ctx = this.ctx,
  150. size = this.size;
  151. if (!fill)
  152. throw Error("The fill is not specified!");
  153. if (fill.color)
  154. this.arcFill = fill.color;
  155. if (fill.gradient) {
  156. var gr = fill.gradient;
  157. if (gr.length == 1) {
  158. this.arcFill = gr[0];
  159. } else if (gr.length > 1) {
  160. var ga = fill.gradientAngle || 0, // gradient direction angle; 0 by default
  161. gd = fill.gradientDirection || [
  162. size / 2 * (1 - Math.cos(ga)), // x0
  163. size / 2 * (1 + Math.sin(ga)), // y0
  164. size / 2 * (1 + Math.cos(ga)), // x1
  165. size / 2 * (1 - Math.sin(ga)) // y1
  166. ];
  167. var lg = ctx.createLinearGradient.apply(ctx, gd);
  168. for (var i = 0; i < gr.length; i++) {
  169. var color = gr[i],
  170. pos = i / (gr.length - 1);
  171. if ($.isArray(color)) {
  172. pos = color[1];
  173. color = color[0];
  174. }
  175. lg.addColorStop(pos, color);
  176. }
  177. this.arcFill = lg;
  178. }
  179. }
  180. if (fill.image) {
  181. var img;
  182. if (fill.image instanceof Image) {
  183. img = fill.image;
  184. } else {
  185. img = new Image();
  186. img.src = fill.image;
  187. }
  188. if (img.complete)
  189. setImageFill();
  190. else
  191. img.onload = setImageFill;
  192. }
  193. function setImageFill() {
  194. var bg = $('<canvas>')[0];
  195. bg.width = self.size;
  196. bg.height = self.size;
  197. bg.getContext('2d').drawImage(img, 0, 0, size, size);
  198. self.arcFill = self.ctx.createPattern(bg, 'no-repeat');
  199. self.drawFrame(self.lastFrameValue);
  200. }
  201. },
  202. draw: function() {
  203. if (this.animation)
  204. this.drawAnimated(this.value);
  205. else
  206. this.drawFrame(this.value);
  207. },
  208. /**
  209. * @protected
  210. * @param {number} v Frame value
  211. */
  212. drawFrame: function(v) {
  213. this.lastFrameValue = v;
  214. this.ctx.clearRect(0, 0, this.size, this.size);
  215. this.drawEmptyArc(v);
  216. this.drawArc(v);
  217. },
  218. /**
  219. * @protected
  220. * @param {number} v Frame value
  221. */
  222. drawArc: function(v) {
  223. var ctx = this.ctx,
  224. r = this.radius,
  225. t = this.getThickness(),
  226. a = this.startAngle;
  227. ctx.save();
  228. ctx.beginPath();
  229. if (!this.reverse) {
  230. ctx.arc(r, r, r - t / 2, a, a + Math.PI * 2 * v);
  231. } else {
  232. ctx.arc(r, r, r - t / 2, a - Math.PI * 2 * v, a);
  233. }
  234. ctx.lineWidth = t;
  235. ctx.lineCap = this.lineCap;
  236. ctx.strokeStyle = this.arcFill;
  237. ctx.stroke();
  238. ctx.restore();
  239. },
  240. /**
  241. * @protected
  242. * @param {number} v Frame value
  243. */
  244. drawEmptyArc: function(v) {
  245. var ctx = this.ctx,
  246. r = this.radius,
  247. t = this.getThickness(),
  248. a = this.startAngle;
  249. if (v < 1) {
  250. ctx.save();
  251. ctx.beginPath();
  252. if (v <= 0) {
  253. ctx.arc(r, r, r - t / 2, 0, Math.PI * 2);
  254. } else {
  255. if (!this.reverse) {
  256. ctx.arc(r, r, r - t / 2, a + Math.PI * 2 * v, a);
  257. } else {
  258. ctx.arc(r, r, r - t / 2, a, a - Math.PI * 2 * v);
  259. }
  260. }
  261. ctx.lineWidth = t;
  262. ctx.strokeStyle = this.emptyFill;
  263. ctx.stroke();
  264. ctx.restore();
  265. }
  266. },
  267. /**
  268. * @protected
  269. * @param {number} v Value
  270. */
  271. drawAnimated: function(v) {
  272. var self = this,
  273. el = this.el;
  274. el.trigger('circle-animation-start');
  275. $(this.canvas)
  276. .stop(true, true)
  277. .css({ animationProgress: 0 })
  278. .animate({ animationProgress: 1 }, $.extend({}, this.animation, {
  279. step: function(animationProgress) {
  280. var stepValue = self.animationStartValue * (1 - animationProgress) + v * animationProgress;
  281. self.drawFrame(stepValue);
  282. el.trigger('circle-animation-progress', [animationProgress, stepValue]);
  283. },
  284. complete: function() {
  285. el.trigger('circle-animation-end');
  286. }
  287. }));
  288. },
  289. /**
  290. * @protected
  291. * @returns {number}
  292. */
  293. getThickness: function() {
  294. return $.isNumeric(this.thickness) ? this.thickness : this.size / 14;
  295. }
  296. };
  297. //-------------------------------------------- Initiating jQuery plugin --------------------------------------------
  298. $.circleProgress = {
  299. // Default options (you may override them)
  300. defaults: CircleProgress.prototype
  301. };
  302. // ease-in-out-cubic
  303. $.easing.circleProgressEasing = function(x, t, b, c, d) {
  304. if ((t /= d / 2) < 1)
  305. return c / 2 * t * t * t + b;
  306. return c / 2 * ((t -= 2) * t * t + 2) + b;
  307. };
  308. /**
  309. * Draw animated circular progress bar.
  310. *
  311. * Appends <canvas> to the element or updates already appended one.
  312. *
  313. * If animated, throws 3 events:
  314. *
  315. * - circle-animation-start(jqEvent)
  316. * - circle-animation-progress(jqEvent, animationProgress, stepValue) - multiple event;
  317. * animationProgress: from 0.0 to 1.0;
  318. * stepValue: from 0.0 to value
  319. * - circle-animation-end(jqEvent)
  320. *
  321. * @param config Example: { value: 0.75, size: 50, animation: false };
  322. * you may set any of public options;
  323. * `animation` may be set to false;
  324. * you may also use .circleProgress('widget') to get the canvas
  325. */
  326. $.fn.circleProgress = function(config) {
  327. var dataName = 'circle-progress';
  328. if (config == 'widget') {
  329. var data = this.data(dataName);
  330. return data && data.canvas;
  331. }
  332. return this.each(function() {
  333. var el = $(this),
  334. instance = el.data(dataName),
  335. cfg = $.isPlainObject(config) ? config : {};
  336. if (instance) {
  337. instance.init(cfg);
  338. } else {
  339. cfg.el = el;
  340. instance = new CircleProgress(cfg);
  341. el.data(dataName, instance);
  342. }
  343. });
  344. };
  345. })(jQuery);