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