jquery-asPieProgress.es.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /**
  2. * jQuery asPieProgress v0.4.7
  3. * https://github.com/amazingSurge/jquery-asPieProgress
  4. *
  5. * Copyright (c) amazingSurge
  6. * Released under the LGPL-3.0 license
  7. */
  8. import $ from 'jquery';
  9. const SvgElement = (tag, attrs) => {
  10. 'use strict';
  11. const elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
  12. if (!attrs) {
  13. return elem;
  14. }
  15. for (let key in attrs) {
  16. if (!Object.hasOwnProperty.call(attrs, key)) {
  17. continue;
  18. }
  19. elem.setAttribute(key, attrs[key]);
  20. }
  21. return elem;
  22. };
  23. if (!Date.now) {
  24. Date.now = () => {
  25. 'use strict';
  26. return new Date().getTime();
  27. };
  28. }
  29. const vendors = ['webkit', 'moz'];
  30. for (let i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
  31. const vp = vendors[i];
  32. window.requestAnimationFrame = window[`${vp}RequestAnimationFrame`];
  33. window.cancelAnimationFrame = (window[`${vp}CancelAnimationFrame`] || window[`${vp}CancelRequestAnimationFrame`]);
  34. }
  35. if (/iP(ad|hone|od).*OS (6|7|8)/.test(window.navigator.userAgent) // iOS6 is buggy
  36. ||
  37. !window.requestAnimationFrame || !window.cancelAnimationFrame) {
  38. let lastTime = 0;
  39. window.requestAnimationFrame = callback => {
  40. 'use strict';
  41. const now = getTime();
  42. const nextTime = Math.max(lastTime + 16, now);
  43. return setTimeout(() => {
  44. callback(lastTime = nextTime);
  45. },
  46. nextTime - now);
  47. };
  48. window.cancelAnimationFrame = clearTimeout;
  49. }
  50. const getTime = () => {
  51. if (typeof window.performance !== 'undefined' && window.performance.now) {
  52. return window.performance.now();
  53. }
  54. return Date.now();
  55. };
  56. const isPercentage = (n) => {
  57. 'use strict';
  58. return typeof n === 'string' && n.indexOf('%') !== -1;
  59. };
  60. const svgSupported = 'createElementNS' in document && new SvgElement('svg', {}).createSVGRect;
  61. const easingBezier = (mX1, mY1, mX2, mY2) => {
  62. 'use strict';
  63. const a = (aA1, aA2) => {
  64. return 1.0 - 3.0 * aA2 + 3.0 * aA1;
  65. };
  66. const b = (aA1, aA2) => {
  67. return 3.0 * aA2 - 6.0 * aA1;
  68. };
  69. const c = (aA1) => {
  70. return 3.0 * aA1;
  71. };
  72. // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  73. const calcBezier = (aT, aA1, aA2) => {
  74. return ((a(aA1, aA2) * aT + b(aA1, aA2)) * aT + c(aA1)) * aT;
  75. };
  76. // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
  77. const getSlope = (aT, aA1, aA2) => {
  78. return 3.0 * a(aA1, aA2) * aT * aT + 2.0 * b(aA1, aA2) * aT + c(aA1);
  79. };
  80. const getTForX = (aX) => {
  81. // Newton raphson iteration
  82. let aGuessT = aX;
  83. for (let i = 0; i < 4; ++i) {
  84. let currentSlope = getSlope(aGuessT, mX1, mX2);
  85. if (currentSlope === 0.0) {
  86. return aGuessT;
  87. }
  88. let currentX = calcBezier(aGuessT, mX1, mX2) - aX;
  89. aGuessT -= currentX / currentSlope;
  90. }
  91. return aGuessT;
  92. };
  93. if (mX1 === mY1 && mX2 === mY2) {
  94. return {
  95. css: 'linear',
  96. fn(aX) {
  97. return aX;
  98. }
  99. };
  100. }
  101. return {
  102. css: `cubic-bezier(${mX1},${mY1},${mX2},${mY2})`,
  103. fn(aX) {
  104. return calcBezier(getTForX(aX), mY1, mY2);
  105. }
  106. };
  107. };
  108. var EASING = {
  109. ease: easingBezier(0.25, 0.1, 0.25, 1.0),
  110. linear: easingBezier(0.00, 0.0, 1.00, 1.0),
  111. 'ease-in': easingBezier(0.42, 0.0, 1.00, 1.0),
  112. 'ease-out': easingBezier(0.00, 0.0, 0.58, 1.0),
  113. 'ease-in-out': easingBezier(0.42, 0.0, 0.58, 1.0)
  114. };
  115. var DEFAULTS = {
  116. namespace: 'asPieProgress',
  117. classes: {
  118. svg: 'pie_progress__svg',
  119. element: 'pie_progress',
  120. number: 'pie_progress__number',
  121. content: 'pie_progress__content'
  122. },
  123. min: 0,
  124. max: 100,
  125. goal: 100,
  126. size: 160,
  127. speed: 15, // speed of 1/100
  128. barcolor: '#ef1e25',
  129. barsize: '4',
  130. trackcolor: '#f2f2f2',
  131. fillcolor: 'none',
  132. easing: 'ease',
  133. numberCallback(n) {
  134. 'use strict';
  135. const percentage = Math.round(this.getPercentage(n));
  136. return `${percentage}%`;
  137. },
  138. contentCallback: null
  139. };
  140. const NAMESPACE$1 = 'asPieProgress';
  141. class asPieProgress {
  142. constructor(element, options) {
  143. this.element = element;
  144. this.$element = $(element);
  145. this.options = $.extend(true, {}, DEFAULTS, options, this.$element.data());
  146. this.namespace = this.options.namespace;
  147. this.classes = this.options.classes;
  148. this.easing = EASING[this.options.easing] || EASING.ease;
  149. this.$element.addClass(this.classes.element);
  150. this.min = this.$element.attr('aria-valuemin');
  151. this.max = this.$element.attr('aria-valuemax');
  152. this.min = this.min ? parseInt(this.min, 10) : this.options.min;
  153. this.max = this.max ? parseInt(this.max, 10) : this.options.max;
  154. this.first = this.$element.attr('aria-valuenow');
  155. this.first = this.first ? parseInt(this.first, 10) : (this.options.first ? this.options.first : this.min);
  156. this.now = this.first;
  157. this.goal = this.options.goal;
  158. this._frameId = null;
  159. this.initialized = false;
  160. this._trigger('init');
  161. this.init();
  162. }
  163. init() {
  164. this.$number = this.$element.find(`.${this.classes.number}`);
  165. this.$content = this.$element.find(`.${this.classes.content}`);
  166. this.size = this.options.size;
  167. this.width = this.size;
  168. this.height = this.size;
  169. this.prepare();
  170. this.initialized = true;
  171. this._trigger('ready');
  172. }
  173. prepare() {
  174. if (!svgSupported) {
  175. return;
  176. }
  177. this.svg = new SvgElement('svg', {
  178. version: '1.1',
  179. preserveAspectRatio: 'xMinYMin meet',
  180. viewBox: `0 0 ${this.width} ${this.height}`
  181. });
  182. this.buildTrack();
  183. this.buildBar();
  184. $(`<div class="${this.classes.svg}"></div>`).append(this.svg).appendTo(this.$element);
  185. }
  186. buildTrack() {
  187. const height = this.size,
  188. width = this.size;
  189. const cx = width / 2,
  190. cy = height / 2;
  191. const barsize = this.options.barsize;
  192. const ellipse = new SvgElement('ellipse', {
  193. rx: cx - barsize / 2,
  194. ry: cy - barsize / 2,
  195. cx,
  196. cy,
  197. stroke: this.options.trackcolor,
  198. fill: this.options.fillcolor,
  199. 'stroke-width': barsize
  200. });
  201. this.svg.appendChild(ellipse);
  202. }
  203. buildBar() {
  204. if (!svgSupported) {
  205. return;
  206. }
  207. const path = new SvgElement('path', {
  208. fill: 'none',
  209. 'stroke-width': this.options.barsize,
  210. stroke: this.options.barcolor
  211. });
  212. this.bar = path;
  213. this.svg.appendChild(path);
  214. this._drawBar(this.first);
  215. this._updateBar();
  216. }
  217. _drawBar(n) {
  218. if (!svgSupported) {
  219. return;
  220. }
  221. this.barGoal = n;
  222. const height = this.size,
  223. width = this.size;
  224. const cx = width / 2,
  225. cy = height / 2,
  226. startAngle = 0;
  227. const barsize = this.options.barsize;
  228. const r = Math.min(cx, cy) - barsize / 2;
  229. this.r = r;
  230. let percentage = this.getPercentage(n);
  231. if (percentage === 100) {
  232. percentage -= 0.0001;
  233. }
  234. const endAngle = startAngle + percentage * Math.PI * 2 / 100;
  235. const x1 = cx + r * Math.sin(startAngle),
  236. x2 = cx + r * Math.sin(endAngle),
  237. y1 = cy - r * Math.cos(startAngle),
  238. y2 = cy - r * Math.cos(endAngle);
  239. // This is a flag for angles larger than than a half circle
  240. // It is required by the SVG arc drawing component
  241. let big = 0;
  242. if (endAngle - startAngle > Math.PI) {
  243. big = 1;
  244. }
  245. // This string holds the path details
  246. const d = `M${x1},${y1} A${r},${r} 0 ${big} 1 ${x2},${y2}`;
  247. this.bar.setAttribute('d', d);
  248. }
  249. _updateBar() {
  250. if (!svgSupported) {
  251. return;
  252. }
  253. const percenage = this.getPercentage(this.now);
  254. const length = this.bar.getTotalLength();
  255. const offset = length * (1 - percenage / this.getPercentage(this.barGoal));
  256. this.bar.style.strokeDasharray = `${length} ${length}`;
  257. this.bar.style.strokeDashoffset = offset;
  258. }
  259. _trigger(eventType, ...params) {
  260. const data = [this].concat(params);
  261. // event
  262. this.$element.trigger(`${NAMESPACE$1}::${eventType}`, data);
  263. // callback
  264. eventType = eventType.replace(/\b\w+\b/g, (word) => {
  265. return word.substring(0, 1).toUpperCase() + word.substring(1);
  266. });
  267. const onFunction = `on${eventType}`;
  268. if (typeof this.options[onFunction] === 'function') {
  269. this.options[onFunction].apply(this, params);
  270. }
  271. }
  272. // Return the percentage based on the current step
  273. getPercentage(n) {
  274. return 100 * (n - this.min) / (this.max - this.min);
  275. }
  276. go(goal) {
  277. const that = this;
  278. this._clear();
  279. if (isPercentage(goal)) {
  280. goal = parseInt(goal.replace('%', ''), 10);
  281. goal = Math.round(this.min + (goal / 100) * (this.max - this.min));
  282. }
  283. if (typeof goal === 'undefined') {
  284. goal = this.goal;
  285. }
  286. if (goal > this.max) {
  287. goal = this.max;
  288. } else if (goal < this.min) {
  289. goal = this.min;
  290. }
  291. if (this.barGoal < goal) {
  292. this._drawBar(goal);
  293. }
  294. const start = that.now;
  295. const startTime = getTime();
  296. const endTime = startTime + Math.abs(start - goal) * 100 * that.options.speed / (that.max - that.min);
  297. const animation = time => {
  298. let next;
  299. if (time > endTime) {
  300. next = goal;
  301. } else {
  302. const distance = (time - startTime) / that.options.speed;
  303. next = Math.round(that.easing.fn(distance / 100) * (that.max - that.min));
  304. if (goal > start) {
  305. next = start + next;
  306. if (next > goal) {
  307. next = goal;
  308. }
  309. } else {
  310. next = start - next;
  311. if (next < goal) {
  312. next = goal;
  313. }
  314. }
  315. }
  316. that._update(next);
  317. if (next === goal) {
  318. window.cancelAnimationFrame(that._frameId);
  319. that._frameId = null;
  320. if (that.now === that.goal) {
  321. that._trigger('finish');
  322. }
  323. } else {
  324. that._frameId = window.requestAnimationFrame(animation);
  325. }
  326. };
  327. that._frameId = window.requestAnimationFrame(animation);
  328. }
  329. _update(n) {
  330. this.now = n;
  331. this._updateBar();
  332. this.$element.attr('aria-valuenow', this.now);
  333. if (this.$number.length > 0 && typeof this.options.numberCallback === 'function') {
  334. this.$number.html(this.options.numberCallback.call(this, [this.now]));
  335. }
  336. if (this.$content.length > 0 && typeof this.options.contentCallback === 'function') {
  337. this.$content.html(this.options.contentCallback.call(this, [this.now]));
  338. }
  339. this._trigger('update', n);
  340. }
  341. _clear() {
  342. if (this._frameId) {
  343. window.cancelAnimationFrame(this._frameId);
  344. this._frameId = null;
  345. }
  346. }
  347. get() {
  348. return this.now;
  349. }
  350. start() {
  351. this._clear();
  352. this._trigger('start');
  353. this.go(this.goal);
  354. }
  355. reset() {
  356. this._clear();
  357. this._drawBar(this.first);
  358. this._update(this.first);
  359. this._trigger('reset');
  360. }
  361. stop() {
  362. this._clear();
  363. this._trigger('stop');
  364. }
  365. finish() {
  366. this._clear();
  367. this._update(this.goal);
  368. this._trigger('finish');
  369. }
  370. destroy() {
  371. this.$element.data(NAMESPACE$1, null);
  372. this._trigger('destroy');
  373. }
  374. static registerEasing(name, ...args) {
  375. EASING[name] = easingBezier(...args);
  376. }
  377. static getEasing(name) {
  378. return EASING[name];
  379. }
  380. static setDefaults(options) {
  381. $.extend(true, DEFAULTS, $.isPlainObject(options) && options);
  382. }
  383. }
  384. var info = {
  385. version:'0.4.7'
  386. };
  387. const NAMESPACE = 'asPieProgress';
  388. const OtherAsPieProgress = $.fn.asPieProgress;
  389. const jQueryAsPieProgress = function(options, ...args) {
  390. if (typeof options === 'string') {
  391. const method = options;
  392. if (/^_/.test(method)) {
  393. return false;
  394. } else if ((/^(get)/.test(method))) {
  395. const instance = this.first().data(NAMESPACE);
  396. if (instance && typeof instance[method] === 'function') {
  397. return instance[method](...args);
  398. }
  399. } else {
  400. return this.each(function() {
  401. const instance = $.data(this, NAMESPACE);
  402. if (instance && typeof instance[method] === 'function') {
  403. instance[method](...args);
  404. }
  405. });
  406. }
  407. }
  408. return this.each(function() {
  409. if (!$(this).data(NAMESPACE)) {
  410. $(this).data(NAMESPACE, new asPieProgress(this, options));
  411. }
  412. });
  413. };
  414. $.fn.asPieProgress = jQueryAsPieProgress;
  415. $.asPieProgress = $.extend({
  416. setDefaults: asPieProgress.setDefaults,
  417. registerEasing: asPieProgress.registerEasing,
  418. getEasing: asPieProgress.getEasing,
  419. noConflict: function() {
  420. $.fn.asPieProgress = OtherAsPieProgress;
  421. return jQueryAsPieProgress;
  422. }
  423. }, info);